멀티미디어 터널링

멀티미디어 터널링을 사용하면 압축된 비디오 데이터가 앱 코드나 Android 프레임워크 코드에 의해 처리되지 않고 하드웨어 비디오 디코더를 통해 디스플레이로 직접 터널링될 수 있습니다. Android 스택 아래의 기기별 코드는 비디오 프레임 프레젠테이션 타임스탬프를 다음 유형의 내부 클록 중 하나와 비교하여 디스플레이에 보낼 비디오 프레임과 보낼 시기를 결정합니다.

  • Android 5 이상에서 주문형 비디오 재생의 경우 앱에서 전달한 오디오 프레젠테이션 타임스탬프와 동기화된 AudioTrack 시계

  • Android 11 이상에서 라이브 방송 재생의 경우 튜너 로 구동되는 프로그램 참조 시계(PCR) 또는 시스템 시간 시계(STC)

배경

Android의 기존 비디오 재생은 압축된 비디오 프레임이 디코딩되면 앱에 알립니다 . 그런 다음 앱은 디코딩된 비디오 프레임을 디스플레이에 해제 하여 해당 오디오 프레임과 동일한 시스템 클록 시간에 렌더링되고, 기록 AudioTimestamps 인스턴스를 검색 하여 정확한 타이밍을 계산합니다.

터널링된 비디오 재생은 앱 코드를 우회하고 비디오에 작용하는 프로세스의 수를 줄이기 때문에 OEM 구현에 따라 보다 효율적인 비디오 렌더링을 제공할 수 있습니다. 또한 비디오 렌더링을 위한 Android 요청의 타이밍과 실제 하드웨어 vsync의 타이밍 사이의 잠재적인 왜곡으로 인해 발생하는 타이밍 문제를 방지하여 선택한 클럭(PRC, STC 또는 오디오)에 보다 정확한 비디오 케이던스와 동기화를 제공할 수 있습니다. 그러나 터널링은 버퍼가 Android 그래픽 스택을 우회하기 때문에 PiP(Picture-in-Picture) 창의 흐림 또는 둥근 모서리와 같은 GPU 효과에 대한 지원을 감소시킬 수도 있습니다.

다음 다이어그램은 터널링이 비디오 재생 프로세스를 단순화하는 방법을 보여줍니다.

전통 모드와 터널 모드 비교

그림 1. 기존 비디오 재생 프로세스와 터널링된 비디오 재생 프로세스 비교

앱 개발자용

대부분의 앱 개발자는 재생 구현을 위해 라이브러리와 통합하기 때문에 대부분의 경우 구현 시 터널링된 재생을 위해 해당 라이브러리를 재구성하기만 하면 됩니다. 터널링된 비디오 플레이어의 저수준 구현의 경우 다음 지침을 사용하십시오.

Android 5 이상에서 주문형 비디오 재생:

  1. SurfaceView 인스턴스를 만듭니다.

  2. audioSessionId 인스턴스를 만듭니다.

  3. 2단계에서 만든 audioSessionId 인스턴스로 AudioTrackMediaCodec 인스턴스를 만듭니다.

  4. 오디오 데이터의 첫 번째 오디오 프레임에 대한 프레젠테이션 타임스탬프를 사용하여 오디오 데이터를 AudioTrack 에 대기열에 넣습니다.

Android 11 이상에서 라이브 방송 재생:

  1. SurfaceView 인스턴스를 만듭니다.

  2. Tuner 에서 avSyncHwId 인스턴스를 가져옵니다.

  3. 2단계에서 생성한 avSyncHwId 인스턴스로 AudioTrackMediaCodec 인스턴스를 생성합니다.

API 호출 흐름은 다음 코드 조각에 나와 있습니다.

aab.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE);

// configure for audio clock sync
aab.setFlag(AudioAttributes.FLAG_HW_AV_SYNC);
// or, for tuner clock sync (Android 11 or higher)
new tunerConfig = TunerConfiguration(0, avSyncId);
aab.setTunerConfiguration(tunerConfig);
if (codecName == null) {
  return FAILURE;
}

// configure for audio clock sync
mf.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, audioSessionId);
// or, for tuner clock sync (Android 11 or higher)
mf.setInteger(MediaFormat.KEY_HARDWARE_AV_SYNC_ID, avSyncId);

주문형 비디오 재생 동작

터널링된 주문형 비디오 재생은 암시적으로 AudioTrack 재생과 연결되어 있기 때문에 터널링된 비디오 재생 동작은 오디오 재생 동작에 따라 달라질 수 있습니다.

  • 대부분의 장치에서 기본적으로 비디오 프레임은 오디오 재생이 시작될 때까지 렌더링되지 않습니다. 그러나 앱은 오디오 재생을 시작하기 전에 비디오 프레임을 렌더링해야 할 수도 있습니다.

    • 대기 중인 첫 번째 비디오 프레임이 디코딩되는 즉시 렌더링되어야 함을 알리려면 PARAMETER_KEY_TUNNEL_PEEK 매개변수를 1 로 설정하십시오. 압축된 비디오 프레임이 대기열에서 재정렬되는 경우(예: B-프레임 이 있는 경우) 이는 첫 번째로 표시되는 비디오 프레임이 항상 I-프레임이어야 함을 의미합니다.

    • 오디오 재생이 시작될 때까지 대기 중인 첫 번째 비디오 프레임을 렌더링하지 않으려면 이 매개변수를 0 으로 설정하십시오.

    • 이 매개변수가 설정되어 있지 않으면 OEM이 장치의 동작을 결정합니다.

  • 오디오 데이터가 AudioTrack 에 제공되지 않고 버퍼가 비어 있으면(오디오 언더런) 오디오 클럭이 더 이상 진행되지 않기 때문에 더 많은 오디오 데이터가 기록될 때까지 비디오 재생이 중단됩니다.

  • 재생하는 동안 앱에서 수정할 수 없는 불연속성이 오디오 프레젠테이션 타임스탬프에 나타날 수 있습니다. 이 경우 OEM은 현재 비디오 프레임을 지연시켜 음수 간격을 수정하고 OEM 구현에 따라 비디오 프레임을 삭제하거나 무음 오디오 프레임을 삽입하여 양수 간격을 수정합니다. 삽입된 무음 오디오 프레임에 대해 AudioTimestamp 프레임 위치가 증가하지 않습니다.

기기 제조업체의 경우

구성

OEM은 터널링된 비디오 재생을 지원하기 위해 별도의 비디오 디코더를 만들어야 합니다. 이 디코더는 media_codecs.xml 파일에서 터널링된 재생이 가능함을 알려야 합니다.

<Feature name="tunneled-playback" required="true"/>

터널링된 MediaCodec 인스턴스가 오디오 세션 ID로 구성되면 이 HW_AV_SYNC ID에 대해 AudioFlinger 를 쿼리합니다.

if (entry.getKey().equals(MediaFormat.KEY_AUDIO_SESSION_ID)) {
    int sessionId = 0;
    try {
        sessionId = (Integer)entry.getValue();
    }
    catch (Exception e) {
        throw new IllegalArgumentException("Wrong Session ID Parameter!");
    }
    keys[i] = "audio-hw-sync";
    values[i] = AudioSystem.getAudioHwSyncForSession(sessionId);
}

이 쿼리 중에 AudioFlinger 는 기본 오디오 장치에서 HW_AV_SYNC ID를 검색하고 이를 오디오 세션 ID와 내부적으로 연결합니다.

audio_hw_device_t *dev = mPrimaryHardwareDev->hwDevice();
char *reply = dev->get_parameters(dev, AUDIO_PARAMETER_HW_AV_SYNC);
AudioParameter param = AudioParameter(String8(reply));
int hwAVSyncId;
param.getInt(String8(AUDIO_PARAMETER_HW_AV_SYNC), hwAVSyncId);

AudioTrack 인스턴스가 이미 생성된 경우 HW_AV_SYNC ID가 동일한 오디오 세션 ID로 출력 스트림에 전달됩니다. 아직 생성되지 않은 경우 HW_AV_SYNC ID는 AudioTrack 생성 중에 출력 스트림으로 전달됩니다. 이것은 재생 스레드 에 의해 수행됩니다.

mOutput->stream->common.set_parameters(&mOutput->stream->common, AUDIO_PARAMETER_STREAM_HW_AV_SYNC, hwAVSyncId);

오디오 출력 스트림 또는 Tuner 구성에 해당하는지 여부에 관계없이 HW_AV_SYNC ID는 OMX 또는 Codec2 구성 요소로 전달되어 OEM 코드가 코덱을 해당 오디오 출력 스트림 또는 튜너 스트림과 연결할 수 있습니다.

구성 요소를 구성하는 동안 OMX 또는 Codec2 구성 요소는 코덱을 HWC(Hardware Composer) 계층과 연결하는 데 사용할 수 있는 측파대 핸들을 반환해야 합니다. 앱이 표면을 MediaCodec 와 연결할 때 이 측파대 핸들은 계층을 측파대 계층으로 구성하는 SurfaceFlinger 를 통해 HWC로 전달됩니다.

err = native_window_set_sideband_stream(nativeWindow.get(), sidebandHandle);
if (err != OK) {
  ALOGE("native_window_set_sideband_stream(%p) failed! (err %d).", sidebandHandle, err);
  return err;
}

HWC는 관련 오디오 출력 스트림 또는 튜너 프로그램 참조 클록에 동기화된 적절한 시간에 코덱 출력에서 ​​새 이미지 버퍼를 수신하고 다른 레이어의 현재 내용과 버퍼를 합성하고 결과 이미지를 표시하는 역할을 합니다. 이는 일반적인 준비 및 설정 주기와 독립적으로 발생합니다. 준비 및 설정 호출은 다른 레이어가 변경되거나 측파대 레이어의 속성(예: 위치 또는 크기)이 변경될 때만 발생합니다.

OMX

터널링된 디코더 구성 요소는 다음을 지원해야 합니다.

  • ConfigureVideoTunnelModeParams 구조를 사용하여 오디오 출력 장치와 연결된 HW_AV_SYNC ID를 전달하는 OMX.google.android.index.configureVideoTunnelMode 확장 매개변수를 설정합니다.

  • 오디오 재생이 시작되었는지 여부에 관계없이 첫 번째 디코딩된 비디오 프레임을 렌더링하거나 렌더링하지 않도록 코덱에 지시하는 OMX_IndexConfigAndroidTunnelPeek 매개변수를 구성합니다.

  • 첫 번째 터널링된 비디오 프레임이 디코딩되어 렌더링할 준비가 되면 OMX_EventOnFirstTunnelFrameReady 이벤트를 보냅니다.

AOSP 구현은 다음 코드 조각과 같이 ACodec 를 통해 OMXNodeInstance 에서 터널 모드를 구성합니다.

OMX_INDEXTYPE index;
OMX_STRING name = const_cast<OMX_STRING>(
        "OMX.google.android.index.configureVideoTunnelMode");

OMX_ERRORTYPE err = OMX_GetExtensionIndex(mHandle, name, &index);

ConfigureVideoTunnelModeParams tunnelParams;
InitOMXParams(&tunnelParams);
tunnelParams.nPortIndex = portIndex;
tunnelParams.bTunneled = tunneled;
tunnelParams.nAudioHwSync = audioHwSync;
err = OMX_SetParameter(mHandle, index, &tunnelParams);
err = OMX_GetParameter(mHandle, index, &tunnelParams);
sidebandHandle = (native_handle_t*)tunnelParams.pSidebandWindow;

구성 요소가 이 구성을 지원하는 경우 HWC가 연결된 코덱을 식별할 수 있도록 이 코덱에 측파대 핸들을 할당하고 pSidebandWindow 구성원을 통해 다시 전달해야 합니다. 구성 요소가 이 구성을 지원하지 않으면 bTunneledOMX_FALSE 로 설정해야 합니다.

코덱2

Android 11 이상에서 Codec2 는 터널링된 재생을 지원합니다. 디코더 구성 요소는 다음을 지원해야 합니다.

  • 터널 모드를 구성하고 오디오 출력 장치 또는 튜너 구성에서 검색된 C2PortTunneledModeTuning 를 전달하는 HW_AV_SYNC 구성.

  • C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE 을 쿼리하여 HWC에 대한 측파대 핸들을 할당하고 검색합니다.

  • 코덱에 디코딩 및 신호 작업 완료를 지시하지만 1) 코덱이 나중에 렌더링하도록 지시하거나 2) 오디오 재생이 시작될 때까지 출력 버퍼를 렌더링하지 않는 C2Work 에 연결될 때 C2_PARAMKEY_TUNNEL_HOLD_RENDER 처리.

  • 오디오 재생이 시작되지 않은 경우에도 C2_PARAMKEY_TUNNEL_HOLD_RENDER 로 표시된 프레임을 즉시 렌더링하도록 코덱에 지시하는 C2_PARAMKEY_TUNNEL_START_RENDER 처리.

  • debug.stagefright.ccodec_delayed_params 구성하지 않은 상태로 둡니다(권장). 구성하는 경우 false 로 설정하십시오.

AOSP 구현은 다음 코드 스니펫과 같이 CCodec 을 통해 C2PortTunnelModeTuning 에서 터널 모드를 구성합니다.

if (msg->findInt32("audio-hw-sync", &tunneledPlayback->m.syncId[0])) {
    tunneledPlayback->m.syncType =
            C2PortTunneledModeTuning::Struct::sync_type_t::AUDIO_HW_SYNC;
} else if (msg->findInt32("hw-av-sync-id", &tunneledPlayback->m.syncId[0])) {
    tunneledPlayback->m.syncType =
            C2PortTunneledModeTuning::Struct::sync_type_t::HW_AV_SYNC;
} else {
    tunneledPlayback->m.syncType =
            C2PortTunneledModeTuning::Struct::sync_type_t::REALTIME;
    tunneledPlayback->setFlexCount(0);
}
c2_status_t c2err = comp->config({ tunneledPlayback.get() }, C2_MAY_BLOCK,
        failures);
std::vector<std::unique_ptr<C2Param>> params;
c2err = comp->query({}, {C2PortTunnelHandleTuning::output::PARAM_TYPE},
        C2_DONT_BLOCK, &params);
if (c2err == C2_OK && params.size() == 1u) {
    C2PortTunnelHandleTuning::output *videoTunnelSideband =
            C2PortTunnelHandleTuning::output::From(params[0].get());
    return OK;
}

구성 요소가 이 구성을 지원하는 경우 이 코덱에 측파대 핸들을 할당하고 HWC가 연결된 코덱을 식별할 수 있도록 C2PortTunnelHandlingTuning 을 통해 다시 전달해야 합니다.

오디오 HAL

주문형 비디오 재생의 경우 오디오 HAL은 앱이 작성하는 오디오 데이터의 각 블록 시작 부분에 있는 헤더 내에서 빅 엔디안 형식의 오디오 데이터와 함께 인라인으로 오디오 프레젠테이션 타임스탬프를 수신합니다.

struct TunnelModeSyncHeader {
  // The 32-bit data to identify the sync header (0x55550002)
  int32 syncWord;
  // The size of the audio data following the sync header before the next sync
  // header might be found.
  int32 sizeInBytes;
  // The presentation timestamp of the first audio sample following the sync
  // header.
  int64 presentationTimestamp;
  // The number of bytes to skip after the beginning of the sync header to find the
  // first audio sample (20 bytes for compressed audio, or larger for PCM, aligned
  // to the channel count and sample size).
  int32 offset;
}

HWC가 해당 오디오 프레임과 동기화하여 비디오 프레임을 렌더링하려면 오디오 HAL이 동기화 헤더를 구문 분석하고 프레젠테이션 타임스탬프를 사용하여 재생 시계를 오디오 렌더링과 다시 동기화해야 합니다. 압축된 오디오가 재생될 때 다시 동기화하려면 오디오 HAL이 압축된 오디오 데이터 내의 메타데이터를 구문 분석하여 재생 시간을 결정해야 할 수 있습니다.

일시 중지 지원

Android 5 이하에는 일시중지 지원이 포함되어 있지 않습니다. A/V 기아 상태에서만 터널링된 재생을 일시 중지할 수 있지만 비디오용 내부 버퍼가 큰 경우(예: OMX 구성 요소에 1초의 데이터가 있는 경우) 일시 중지가 응답하지 않는 것처럼 보입니다.

Android 5.1 이상에서 AudioFlinger 는 직접(터널링된) 오디오 출력에 대한 일시 중지 및 재개를 지원합니다. HAL이 일시중지 및 재개를 구현하는 경우 트랙 일시중지 및 재개가 HAL로 전달됩니다.

일시 중지, 플러시, 재개 호출 시퀀스는 재생 스레드에서 HAL 호출을 실행하여 준수합니다(오프로드와 동일).

구현 제안

오디오 HAL

Android 11의 경우 PCR 또는 STC의 HW sync ID를 A/V 동기화에 사용할 수 있으므로 비디오 전용 스트림이 지원됩니다.

Android 10 이하의 경우 터널링된 동영상 재생을 지원하는 기기에는 audio_policy.conf 파일에 FLAG_HW_AV_SYNCAUDIO_OUTPUT_FLAG_DIRECT 플래그가 있는 오디오 출력 스트림 프로필이 하나 이상 있어야 합니다. 이 플래그는 오디오 클럭에서 시스템 클럭을 설정하는 데 사용됩니다.

OMX

장치 제조업체에는 터널링된 비디오 재생을 위한 별도의 OMX 구성 요소가 있어야 합니다(제조업체는 보안 재생과 같은 다른 유형의 오디오 및 비디오 재생을 위한 추가 OMX 구성 요소를 가질 수 있음). 터널링된 구성 요소는 다음을 수행해야 합니다.

  • 출력 포트에 0 버퍼( nBufferCountMin , nBufferCountActual )를 지정합니다.

  • OMX.google.android.index.prepareForAdaptivePlayback setParameter 확장을 구현합니다.

  • media_codecs.xml 파일에서 해당 기능을 지정하고 터널링된 재생 기능을 선언합니다. 또한 프레임 크기, 정렬 또는 비트 전송률에 대한 제한 사항을 명확히 해야 합니다. 예가 아래에 나와 있습니다.

    <MediaCodec name="OMX.OEM_NAME.VIDEO.DECODER.AVC.tunneled"
    type="video/avc" >
        <Feature name="adaptive-playback" />
        <Feature name="tunneled-playback" required=”true” />
        <Limit name="size" min="32x32" max="3840x2160" />
        <Limit name="alignment" value="2x2" />
        <Limit name="bitrate" range="1-20000000" />
            ...
    </MediaCodec>
    

터널링 및 비터널 디코딩을 지원하는 데 동일한 OMX 구성 요소가 사용되는 경우 터널링된 재생 기능을 필요하지 않은 상태로 유지해야 합니다. 그러면 터널링된 디코더와 터널링되지 않은 디코더 모두 동일한 기능 제한이 있습니다. 예가 아래에 나와 있습니다.

<MediaCodec name="OMX._OEM\_NAME_.VIDEO.DECODER.AVC" type="video/avc" >
    <Feature name="adaptive-playback" />
    <Feature name="tunneled-playback" />
    <Limit name="size" min="32x32" max="3840x2160" />
    <Limit name="alignment" value="2x2" />
    <Limit name="bitrate" range="1-20000000" />
        ...
</MediaCodec>

하드웨어 작곡가(HWC)

디스플레이에 터널링된 레이어( HWC_SIDEBAND compositionType 이 있는 레이어)가 있는 경우 레이어의 sidebandStream 은 OMX 비디오 구성 요소에 의해 할당된 사이드밴드 핸들입니다.

HWC는 디코딩된 비디오 프레임(터널링된 OMX 구성 요소에서)을 연결된 오디오 트랙( audio-hw-sync ID 포함)과 동기화합니다. 새 비디오 프레임이 최신이 되면 HWC는 마지막 준비 또는 설정 호출 중에 수신된 모든 계층의 현재 내용과 이를 합성하고 결과 이미지를 표시합니다. 준비 또는 설정 호출은 다른 레이어가 변경되거나 측파대 레이어의 속성(예: 위치 또는 크기)이 변경될 때만 발생합니다.

다음 그림은 오디오(7c)를 기반으로 정확한 시간에 표시하기 위해 최신 구성(7a)과 비디오 프레임(7b)을 결합하기 위해 하드웨어(또는 커널 또는 드라이버) 동기화 장치와 함께 작동하는 HWC를 나타냅니다.

오디오를 기반으로 비디오 프레임을 결합하는 HWC

그림 2. HWC 하드웨어(또는 커널 또는 드라이버) 싱크로나이저