자바스크립트를 활성화 해주세요

유튜브 플레이어 컨트롤러 제작기 - 01

 ·   ·  ☕ 10 min read  ·  ✍️ Yogo

PC에서 게임을 하거나 음악을 들을 때 간혹 소리가 너무 작거나 또는 큰 이유로 볼륨을 조절해야할 때가 종종 있습니다. 외장 DAC이나 사운드 카드 또는 스피커를 사용 시 물리적인 볼륨조절 노브(knob)가 있으면 그 것들을 이용하면 되지만 헤드폰이나 이어폰 또는 물리적인 볼륨 조절이 어려운 경우는 작업표시줄 하단의 스피커 볼륨 조절을 이용해야합니다.

일반적인 상황에서는 큰 불편함은 없지만 전체화면으로 게임을 하는 경우 조절을 위해서는 Alt + Tab으로 빠져나오거나 또는 윈도우 키를 눌러서 작업표시줄이 보이도록 한 후에 조절을 해야하는 경우가 있어서 약간의 불편한 점이 있습니다. 물론 인 게임 내에서 조절도 가능하지만 볼륨을 쉽게 조절하는 방법이 있으면 편리하겠지요?

이러한 요구사항을 반영하여 일부 게이밍 또는 멀티미디어 키보드는 별도의 미디어 제어 버튼을 가지고 있고, 이 버튼을 통해 쉽게 조절이 가능합니다.

mediakey

하지만 전용 키보드가 아닌 일반 기계식 키보드나 아님 폼팩터가 작고 슬림한 키보드는 이러한 키를 가지고 있지 않은 것들이 대부분이라서, 이럴때는 별도의 장치를 사용할 수도 있습니다.

volume-controller

이 프로젝트는 위에 나열한 기기들처럼 윈도우 볼륨과 미디어 재생/트랙 제어가 가능한 미디어 조절기를 만드는 것입니다.

초기 목표 및 요구사항

  • 멋진 노브와 엔코더(encoder)를 이용한 볼륨 조절
  • 물리적인 키를 이용하여 재생/정지, 트랙 이동 등 미디어 제어

초기 목표는 윈도우에서 미디어를 제어하는 것뿐입니다. 대신 기왕이면 고급(알루미늄 노브) 부품을 사용해서 그럴 듯 하게 만들어보려고 합니다.

사전 지식

미디어 컨트롤러 설계와 제작에 앞서 전공 관련 내용이 어려우신 분들을 위해 이해를 돕는 사전 설명을 하고자 합니다.

Quadrature Encoder (이하 쿼드 엔코더)

Quadrature 라는 용어는 공학별로 약간씩 의미는 다르지만 전체적으로 직각 또는 직사각형과 관련된 의미를 지닌 것으로 보입니다. 수학에서는 구적법, 천문학에서는 지구 시점에서 외행성이 태양과 직각(90, 270도) 방향인 상태, 전자 공학에서는 직각 위상(位相)이라는 뜻으로 쓰입니다.1

보통 위상이 다르다고 할 때에는 반복되는 신호가 시간 축을 기준으로 앞뒤로 이동한 상태인데, 실제로 쿼드 엔코더는 2개의 채널이 있고, 회전 시 두 개의 채널의 논리 값이 다음 그림 처럼 90도 위상차가 발생합니다.

Quadrature
이미지 출처2

이해를 쉽게 돕기 위해서 만약 180도 위상차가 난다고 가정한다면 A 채널이 하이 상태(high state, 이하 1)되면 B 채널은 로우 상태(low state, 이하 0)가 되고 반대로 A가 0이면 B가 1이 되므로 A와 B는 서로 상반된 신호 출력을 하게 될 것 입니다.
(하이, 로우 상태는 뭘까 싶으신 분들은 쉽게 스위치가 붙었다 떼였다하는 상태라고 생각하시면 됩니다. 여기서는 디지털적인 표현으로 1과 0으로 표현합니다)

쿼드 엔코더는 90도 차이가 나므로 두 채널의 신호는 시간(t)/2 만큼 위상 차이가 나고 각 채널의 상태가 변경되는(trasition) 시점을 기준으로 두 채널의 상태를 보면 총 4가지의 경우의 수([0, 0], [0, 1], [1, 0], [1, 1])를 가질 수 있습니다. 쉽게 말하면 두개의 스위치를 모두 뗀 것, 하나만 누른것, 둘다 누른 경우를 따진 것 입니다.

그리고 회전 방향에 따라 4가지 상태가 일정한 패턴으로 반복되는데 이를 통해서 회전 방향과 회전량을 추측할 수 있습니다.

오디오나 기타 기기에서 볼륨 조절기에 사용되는 엔코더의 경우 물리적 회전 시 걸림없이 부드럽게 돌아가거나 또는 특정 반경 회전 시 걸리는(pulse) 느낌의 클릭감이 있는데, 후자의 경우 하나의 펄스(이하 클릭)당 4가지 패턴이 1 사이클로 나타납니다.

엔코더 신호 읽기

쿼드 엔코더는 모터 회전량 제어를 위해서 많이 쓰이기도 하므로 모터 제어(motor controll) 전용 MCU 경우 엔코더를 읽을 수 있는 장치(peripheral)를 내장하기도 합니다. 일반 용도(general purpose) MCU의 경우에는 이러한 장치가 없는 경우가 많으므로, 이 때에는 두 채널을 GPIO(General Purpose Input/Output) 포트에 연결한 후 변화상태를 읽으면 되는데, 예를 들어 엔코더의 A, B채널을 PORTA의 0번, 1번 PIN에 각각 할당한 뒤 PORTA의 레지스터 값을 0x03으로 마스킹하면, 엔코더의 신호 패턴을 2bit 크기의 숫자처럼 읽을 수 있습니다.

1
uint8_t encoder_value = PORTA & 0x03;

그럼 심화과정으로 위의 방법을 사용하여 클릭감이 있는 쿼드 엔코더를 기준으로 1사이클 단위로 신호의 변화 값을 읽어서 회전 방향과 회전량(사이클 증가량)을 측정해보겠습니다.

쿼드 엔코더를 위한 별도의 장치가 없는 MCU를 가정하여, 엔코더 신호 변화에 따른 각각 다른 패턴을 보기 위해서는 GPIO 핀(PIN) 인터럽트에 의한 이벤트(event-driven) 방식, 주기적으로 GPIO 포트를 읽는 폴링(polling) 방식 사용이 가능합니다. 참고로 전자의 경우에는 상승, 하강(rising/falling) 인터럽트가 모두 가능해야 디코딩이 수월합니다.

여기서는 폴링 방식을 사용할 예정인데 주기를 USB의 SOF(Start of Frame) 이벤트에 맞추었습니다.

폴링 방식을 사용할 경우 조건이 있는데, 엔코더 신호가 변하는 비율(rate)보다 읽는 비율이 더 높아야 합니다(가급적 2배수 이상), USB의 SOF 이벤트는 호스트에 의해 1ms(1kHz)마다 발생하므로 비교적 높은 수치이기 때문에 모터 회전이 아닌 사람이 손으로 돌리는 엔코더를 읽어오는데는 큰 문제가 없을 것으로 예상 됩니다.

그리고 폴링 방식은 인터럽트 방식과 달리 패턴신호가 바뀌지 않음에도 정해진 시간에 지속적으로 값을 읽어서 중복 패턴값을 읽을 수 있으므로 이전 값과 현재 값이 다름을 확인하여 패턴의 변화를 감지합니다.

프로젝트에는 PEC12R-4225F-S00243라는 엔코더를 사용하였습니다. 1회전(360° Rotation) 시 24의 펄스(클릭) 발생이 가능합니다. 이 엔코더는 비 회전 상태인 경우(펄스와 펄스사이에 위치) 두 채널 상태는 모두 [0, 0]이고 2bit 크기의 숫자값으로 패턴 변화를 본다면, 시계 방향(CW)으로 1펄스(클릭) 회전 시 1 –> 3 –> 2 –> 0 순으로, 역시계 방향(CCW) 회전 시 2 –> 3 –> 1 –> 0 순으로 패턴 변화를 보입니다.

결론적으로 4번의 패턴을 누적하여 체크하면 회전을 어느 방향으로 했는지, 1 클릭 증가를 했는지 확인이 가능하므로 4개의 버퍼크기를 가진 큐(queue)를 만들어 패턴 변화를 확인하면 될 것 같습니다.

엔코더 채널을 읽는데에는 2bit 크기로 패턴 4개만 필요하므로 byte(8bit) 단위의 변수에 좌측으로 2비트씩 쉬프팅 하면서 누적하면 적은 자원과 쉬운 방법으로 패턴 인식이 가능합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#define ENCODER_CW   ((0x01 << 6) | (0x03 << 4) | (0x02 << 2) | 0x00)
#define ENCODER_CCW   ((0x02 << 6) | (0x03 << 4) | (0x01 << 2) | 0x00)

uint8_t encoder_value = 0;
uint8_t previous_channel_value = 0;

void read_encoder() {

  uint8_t channel_value = PORTA & 0x03;

  if (previous_value != encoder_value) {

        encoder_value = (encoder_value << 2) | channel_value;

        switch (encoder_value) {
            case ENCODER_CW:
                // increment
                break;

            case ENCODER_CCW:                
                // decrement
                break;

            default:                
                break;
        }  

  }

  previous_channel_value = channel_value;

}

USB Report Descriptor

HID 키보드에서 전송가능한 키코드를 확인하였을 시 Volume up, down에 대한 별도 키 코드가 존재하는 것을 확인하였습니다. 지난 프로젝트인 모스 키보드처럼 USB 키보드로서 동작을 하도록 하고 이 값을 전송할 시 Windows 10에서 볼륨 조절이 되지 않습니다.

볼륨이나 미디어 재생 등을 제어하기 위해서는 HID 키보드의 역할로는 제어를 할 수 없고 미디어 컨트롤 역할을 수행 할 수 있도록 별도의 명세가 필요합니다.

USB는 디바이스, 컨피그레이션, 인터페이스, 엔드포인드 디스크립터 등 많은 디스크립터를 정의하여 호스트에 연결 시 해당 정보를 교환합니다. 키보드 역할이나 미디어 컨트롤 역할이나 HID 클래스 내의 장치인 것은 동일하므로 대부분 디스크립터에 대한 변경은 필요없고, 실제 엔드포인트에서 주고 받을 정보를 명세한 HID 리포트 디스크립터(report descriptor)를 미디어 컨트롤이 가능하도록 정의가 필요합니다.

CONSUMER CONTROL

HID 장치는 Usage Page라는 항목을 지정함으로써 Report 성격을 결정할 수 있습니다. 키보드 입력 정보를 주고 받기 위해서는 Generic Desktop Page (0x01)라고 정의해야하고, 미디어 제어를 위해서는 Consumer Page (0x0C)4로 정의합니다. 그리고 하위영역에 Consumer Control을 정의합니다.

우리가 보통 미디어 컨트롤을 할 때에는 보통 버튼 입력을 사용하기 때문에 on/off 정보만 필요하므로 굳이 큰 자료형이 필요가 없습니다. 0, 1 비트 단위의 정보 교환이 가능하므로 여러 버튼(컨트롤)을 플래그(flag)로 정의하여 1바이트에 실어 보낼 수 있도록 합니다.

제어는 볼륨 조절(up, down), 뮤트, 트랙 제어(이전/이후), 재생만 할 것이므로 총 6비트만 필요합니다. 나머지 2비트는 사용하지 않을 키 값을 채워도 되겠지만 여기서는 padding 값으로 2비트를 지정합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
const uint8_t hid_report[] =
{  
    0x05, 0x0C, //  Usage Page (Consumer Device)
    0x09, 0x01, //  Usage (Consumer Control)
    0xA1, 0x01, //  Collection (Application)
    0x05, 0x0C, //  Usage Page (Consumer Devices)
    0x15, 0x00, //  Logical Minimum (0), bit value
    0x25, 0x01, //  Logical Maximum (1), bit value
    0x75, 0x01, //  Report Size (1)
    0x95, 0x06, //  Report Count (6)
    0x09, USB_HID_CONSUMER_SCAN_NEXT_TRACK,         // Usage
    0x09, USB_HID_CONSUMER_SCAN_PREVIOUS_TRACK,     // Usage
    0x09, USB_HID_CONSUMER_PLAY_PAUSE,              // Usage (Play / Pause)
    0x09, USB_HID_CONSUMER_MUTE,                    // Usage
    0x09, USB_HID_CONSUMER_VOLUME_INCREMENT,        // Usage
    0x09, USB_HID_CONSUMER_VOLUME_DECREMENT,        // Usage
    0x81, 0x02, //  Input (Data, Variable, Absolute)
    0x95, 0x02, //  Report Count (2)
    0x81, 0x01, //  Input (Constant)
    0xC0
};

회로 설계

엔코더나 버튼 등을 입력포트에 잘 매칭만 하면되므로 회로 설계 시 크게 고려할 사항은 없습니다. 참고로 Pull-up, down 처리는 MCU 내부에서 처리가 되도록 되어 있습니다. 키는 기계식 키 4개와 일반 택트 스위치 1개 엔코더에 달린 키까지 총 6개의 입력이 가능하고 엔코더 디코딩 시 시계/반시계 방향 인식으로 총 8가지의 액션이 가능합니다. 앞서 디스크립터에 정의한 액션은 총 6가지 이므로 부가적으로 2가지 액션이 가능합니다.

회로도는 이글 캐드로 작성하였습니다.

schematic

제작

controller

지난번과 동일하게 테스트 보드를 대상으로 펌웨어를 작성하여 예상한 대로 동작하는지 선행 작업을 진행합니다. 기초 회로와 펌웨어 검증이 어느정도 완료되면 예상 레이아웃을 배치해보고 제작을 시작합니다.

controller

만능기판에 기계식 키를 바로 붙이는 것이 어려워서 hot swap socket을 기판에 고정하고 그 위에 꼽는식으로 하여 고정을 하도록 하였습니다. 체결이 단단하지는 않지만 일단 제작에 고생을 덜하는 것에만 만족하기로 했습니다.

controller

부품을 늘여놓고 땜질을 시작합니다.

controller

완료된 작품 입니다!

controller

회로 검증과 디버깅, 입력 테스트를 거친 후 펌웨어 세부 작업을 진행합니다.

controller

테스트 및 동작

demo

https://youtu.be/gxZb6ow81-E

포커싱이 잘 안맞아 흐릿하게 나오긴 했지만 엔코더를 돌리면 볼륨 조절이 되는 것을 확인 하실 수 있습니다.

윈도우 10에서는 브라우저에 유튜브가 재생중이면 제목, 스크린샷 정보 표시와 함께 볼륨 조절이 됩니다.

미디어 컨트롤러도 무난하게 완성했으니 프로젝트 이것으로 끝!?

낼 수도 있겠지만 이것만 만들려고 시작한 프로젝트는 아니오니 추가 기능을 고려해봅니다. 그럼 어떤 기능을 추가로 구현할 것인지 고민하기전에 현재 구현된 미디어 조절기의 한계점을 짚어보겠습니다.

유튜브 재생 시 한계점과 추가 요구사항

유튜브에서는 볼륨과 재생 외에 이전/다음 트랙 이동 기능은 오직 플레이 리스트에서만 동작합니다.

playlist

유튜브에서는 단일 영상을 묶어서 플레이 리스트로 만들 수 있는데(믹스), 이렇게 영상이 연속적으로 있는 경우에만 트랙 이동이 가능한 상태가 됩니다.

일반 단일 유튜브 영상은 컨트롤 패널을 보면 이전 버튼이 없고 다음 이동 버튼만 있는데, 다음 영상은 엄밀하게는 추천 영상이므로 이전/다음 트랙 이동 버튼으로 이동 할 수 있는 대상이 아닙니다.

아래 이미지를 참고하면 단일 영상과 플레이 리스트 영상일 때 패널 모양이 약간 다른 것을 확인 할 수 있습니다.
상단 이미지는 단일 영상만 재생했을때 패널 모습이고, 하단 이미지는 믹스(플레이리스트)의 영상을 재생했을 때 패널 모습입니다.

difference

여튼 단일 영상에서도 다음 추천 영상으로 이동할 수 있으면 합니다.

그리고 최근에는 타임라인을 보면 챕터(chapter)로 분리된 영상들이 있습니다. 이 영상은 단일 영상에서 점프가 가능한 구간으로 영상 내에서 특정 구간으로 이동을 쉽게 해줍니다. 이 구간을 미디어 컨트롤러로 이동할 수 있으면 좋을 것 같습니다.

chapter

이 외에도 미디어 볼륨 조절은 윈도우 전체 볼륨을 조절하는 기능인데 유튜브만 조절하거나 음소거를 할 수 있으면 유용할 것 같습니다.
그리고 음악이나 영상을 빠르게 앞으로 돌리거나 되돌리는 조그셔틀 같은 기능도 있으면 좋겠지요?

생각하다보니 유튜브 전용 제어 기능만 추가하더라도 미디어 컨트롤러의 활용성이 많이 늘어 날 것 같습니다.

그럼 다음 목표는 유튜브 전용 컨트롤 기능 구현 입니다.

다음 포스트에서 계속…!