Tunelowanie multimediów

Tunelowanie multimedialne umożliwia przesyłanie danych z skompresowanego wideo przez dekoder wideo sprzętowy bezpośrednio do wyświetlacza bez przetwarzania przez kod aplikacji lub kod platformy Android. Kod specyficzny dla urządzenia poniżej warstwy Androida określa, które klatki wideo wysłać na wyświetlacz i kiedy, porównując sygnatury czasowe prezentacji klatek wideo z jednym z tych typów zegara wewnętrznego:

  • W przypadku odtwarzania filmów na żądanie w Androidzie w wersji 5 lub nowszej aplikacja przesyła AudioTrackzegar zsynchronizowany z sygnaturami czasowymi prezentowania dźwięku przekazany.

  • W przypadku odtwarzania transmisji na żywo w Androidzie 11 lub nowszym: zegar odniesienia programu (PCR) lub zegar systemowy (STC) obsługiwany przez tuner.

Tło

Tradycyjne odtwarzanie wideo na Androidzie powiadamia aplikację, gdy zostanie zdekodowany skompresowany kadr wideo. Następnie aplikacja zwalni z pamięci dekodowany obraz wideo, aby wyświetlić go w ramach tego samego czasu zegara systemowego co odpowiadający mu obraz audio, pobierając historyczne AudioTimestampsprzypadki, aby obliczyć prawidłowy czas.

Odtwarzanie wideo w tunelu omija kod aplikacji i zmniejsza liczbę procesów działających na filmie, co może zapewnić wydajniejsze renderowanie wideo w zależności od implementacji OEM. Może też zapewnić dokładniejsze tempo i synchronizację wideo z wybranym zegarkiem (PRC, STC lub audio), unikając problemów z synchronizacją spowodowanych przez potencjalne odchylenia między czasem żądań Androida na potrzeby renderowania wideo a czasem prawdziwych sygnałów synchronizacji sprzętowej. Jednak tunelowanie może też ograniczyć obsługę efektów GPU, takich jak rozmycie lub zaokrąglone rogi w oknach obrazu w obrazie, ponieważ bufory omijają warstwę graficzną Androida.

Ten diagram pokazuje, jak tunelowanie upraszcza proces odtwarzania filmów.

Porównanie trybów tradycyjnego i tunelowego

Rysunek 1. Porównanie tradycyjnych i tunelowych procesów odtwarzania filmów

Dla deweloperów aplikacji

Większość deweloperów aplikacji integruje się z biblioteką w celu odtwarzania, dlatego w większości przypadków implementacja wymaga tylko jej ponownego skonfigurowania pod kątem odtwarzania tunelowanego. Aby zaimplementować tunelowany odtwarzacz wideo na niskim poziomie, postępuj zgodnie z tymi instrukcjami.

Aby odtwarzać filmy na żądanie w Androidzie 5 lub nowszym:

  1. Utwórz instancję SurfaceView.

  2. Utwórz instancję audioSessionId.

  3. Utwórz instancje AudioTrack i MediaCodec z instancją audioSessionId utworzoną w kroku 2.

  4. Dane audio są umieszczane w kolejce AudioTrack z sygnaturą czasową prezentacji dla pierwszego kadru audio w danych audio.

Aby odtwarzać transmisje na żywo na Androidzie 11 lub nowszym:

  1. Utwórz instancję SurfaceView.

  2. Pobierz wystąpienie avSyncHwIdTuner.

  3. Utwórz instancje AudioTrack i MediaCodec z instancją avSyncHwId utworzoną w kroku 2.

Przepływ wywołania interfejsu API jest pokazany w tych fragmentach kodu:

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);

Działanie odtwarzania filmów na żądanie

Ponieważ tunelowanie odtwarzania wideo na żądanie jest powiązane z odtwarzaniem AudioTrack, odtwarzanie tunelowane może zależeć od odtwarzania dźwięku.

  • Na większości urządzeń domyślnie nie renderuje się klatki wideo, dopóki nie rozpocznie się odtwarzanie dźwięku. Aplikacja może jednak potrzebować renderowania klatki filmu przed rozpoczęciem odtwarzania dźwięku, aby pokazać użytkownikowi bieżącą pozycję filmu podczas przewijania.

    • Aby zasygnalizować, że pierwsza klatka wideo w kolejce powinna zostać wyrenderowana zaraz po jej dekodowaniu, ustaw parametr PARAMETER_KEY_TUNNEL_PEEK na 1. Gdy zapisane w kole zagęszczenia klatki wideo zostaną w niej przetasowane (np. gdy występują klatki B), pierwsza wyświetlana klatka wideo powinna być zawsze klatką I.

    • Jeśli nie chcesz, aby pierwsza klatka filmu w kolejce została wyrenderowana przed rozpoczęciem odtwarzania dźwięku, ustaw ten parametr na 0.

    • Jeśli ten parametr nie jest ustawiony, producent OEM określa zachowanie urządzenia.

  • Gdy do AudioTrack nie są dostarczane dane audio i bufory są puste (brak dźwięku), odtwarzanie filmu zostaje wstrzymane, dopóki nie zostaną zapisane kolejne dane audio, ponieważ zegar audio nie jest już przesuwany.

  • Podczas odtwarzania w sygnaturach czasowych prezentacji audio mogą pojawić się nieciągłości, których aplikacja nie może skorygować. W takim przypadku OEM koryguje ujemne luki, opóźniając bieżącą klatkę wideo, a dodatnie luki, pomijając klatki wideo lub wstawiając klatki z cichym dźwiękiem (w zależności od implementacji OEM). Pozycja ramki AudioTimestamp nie zwiększa się w przypadku wstawionych cichych ramek audio.

Informacje dla producentów urządzeń

Konfiguracja

Producenci OEM powinni utworzyć osobny dekoder wideo, aby obsługiwać odtwarzanie wideo w tunelu. Dekoder powinien informować, że obsługuje odtwarzanie w tunelu w pliku media_codecs.xml:

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

Gdy tunelowana instancja MediaCodec jest skonfigurowana z identyfikatorem sesji audio, wysyła zapytanie do AudioFlinger o ten identyfikator HW_AV_SYNC:

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);
}

Podczas tego zapytania AudioFlinger pobiera identyfikator HW_AV_SYNC z głównego urządzenia audio i wewnętrznie wiąże go z identyfikatorem sesji audio:

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);

Jeśli instancja AudioTrack została już utworzona, identyfikator HW_AV_SYNC jest przekazywany do strumienia wyjściowego z tym samym identyfikatorem sesji audio. Jeśli nie został jeszcze utworzony, identyfikator HW_AV_SYNC jest przekazywany do strumienia wyjściowego podczas tworzenia AudioTrack. Wykonuje to wątek odtwarzania:

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

Identyfikator HW_AV_SYNC, niezależnie od tego, czy odpowiada strumieniowi wyjściowemu audio czy konfiguracji Tuner, jest przekazywany do komponentu OMX lub Codec2, aby kod OEM mógł powiązać kodek z odpowiednim strumieniem wyjściowym audio lub strumieniem tunera.

Podczas konfiguracji komponentu komponent OMX lub Codec2 powinien zwrócić obiekt sideband, który można wykorzystać do powiązania kodeka z warstwą kompozytora sprzętowego (HWC). Gdy aplikacja powiąże powierzchnię z MediaCodec, ten uchwyt sideband jest przekazywany do HWC przez SurfaceFlinger, co powoduje skonfigurowanie warstwy jako warstwy sideband.

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 odpowiada za odbiór nowych buforów obrazu z wyjścia kodeka we właściwym czasie, synchronizując je z powiązanym strumieniem wyjściowym audio lub ze zegarkiem odniesienia programu tunera, komponując bufory z bieżącą zawartością innych warstw i wyświetlając uzyskany obraz. Dzieje się tak niezależnie od normalnego cyklu przygotowywania i ustawiania. Wywołania prepare i set występują tylko wtedy, gdy zmieniają się inne warstwy lub właściwości warstwy pobocznej (np. pozycja lub rozmiar).

OMX

Komponent dekodera tunelowanego powinien obsługiwać te funkcje:

  • Ustawienie rozszerzonego parametru OMX.google.android.index.configureVideoTunnelMode, który używa struktury ConfigureVideoTunnelModeParams do przekazywania identyfikatora HW_AV_SYNC powiązanego z urządzeniem wyjściowym audio.

  • Konfiguracja parametru OMX_IndexConfigAndroidTunnelPeek, który informuje kodek, czy ma renderować pierwszą odkodowaną klatkę wideo, czy nie, niezależnie od tego, czy rozpoczęto odtwarzanie dźwięku.

  • Wysyłanie zdarzenia OMX_EventOnFirstTunnelFrameReady, gdy pierwsza tunelowana klatka wideo została zdekodowana i jest gotowa do wyrenderowania.

Implementacja AOSP konfiguruje tryb tunelowania w ACodec OMXNodeInstancejak pokazano w tym fragmencie kodu:

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;

Jeśli komponent obsługuje tę konfigurację, powinien przypisać temu kodekowi uchwyt sideband i przekazać go z powrotem przez element pSidebandWindow, aby HWC mógł zidentyfikować powiązany kodek. Jeśli komponent nie obsługuje tej konfiguracji, powinien ustawić wartość bTunneled na OMX_FALSE.

Codec2

W Androidzie 11 lub nowszym Codec2 obsługuje odtwarzanie tunelowane. Dekoder powinien obsługiwać te funkcje:

  • Konfigurowanie C2PortTunneledModeTuning, które konfiguruje tryb tunelowania i przekazuje HW_AV_SYNC pobrane z urządzenia wyjściowego audio lub z konfiguracji tunera.

  • Wysyłanie zapytania C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE w celu przydzielenia i pobrania uchwytu sideband dla HWC.

  • Obsługa C2_PARAMKEY_TUNNEL_HOLD_RENDER w przypadku załączenia do C2Work, co instruuje kodek do dekodowania i sygnalizowania zakończenia pracy, ale nie do renderowania bufora wyjściowego, dopóki 1) kodek nie otrzyma instrukcji renderowania lub 2) nie rozpocznie się odtwarzanie dźwięku.

  • Obsługa C2_PARAMKEY_TUNNEL_START_RENDER, która instruuje kodek, aby natychmiast wyrenderował ramkę oznaczoną jako C2_PARAMKEY_TUNNEL_HOLD_RENDER, nawet jeśli odtwarzanie dźwięku nie zostało jeszcze rozpoczęte.

  • Pozostaw opcję debug.stagefright.ccodec_delayed_params nieskonfigurowaną (zalecane). Jeśli chcesz skonfigurować tę opcję, ustaw ją na false.

Implementacja AOSP konfiguruje tryb tunelowania w CCodec za pomocą C2PortTunnelModeTuning, jak pokazano w tym fragmencie kodu:

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;
}

Jeśli komponent obsługuje tę konfigurację, powinien przypisać temu kodekowi uchwyt sideband i przekazać go z powrotem przez C2PortTunnelHandlingTuning, aby HWC mógł zidentyfikować powiązany kodek.

Interfejs HAL dźwięku

W przypadku odtwarzania wideo na żądanie interfejs Audio HAL otrzymuje sygnatury czasowe prezentowania dźwięku wbudowane w dane audio w formacie big-endian w nagłówku znajdującym się na początku każdego bloku danych audio zapisywanych przez aplikację:

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;
}

Aby HWC mógł renderować klatki wideo w zsynchronizowany sposób z odpowiednimi klatkami dźwięku, interfejs Audio HAL powinien przeanalizować nagłówek synchronizacji i użyć sygnatury czasowej wyświetlania, aby ponownie zsynchronizować zegar odtwarzania z renderowaniem dźwięku. Aby ponownie zsynchronizować odtwarzanie skompresowanego dźwięku, HAL dźwięku może potrzebować zanalizowania metadanych zawartych w skompresowanych danych dźwięku w celu określenia czasu odtwarzania.

Wstrzymywanie obsługi

Android 5 lub starszy nie obsługuje funkcji wstrzymywania. Odtwarzanie tunelowane można wstrzymać tylko przez brak danych audio-wizualnych, ale jeśli wewnętrzny bufor wideo jest duży (np. w komponencie OMX jest 1 s danych), wstrzymanie może wyglądać na niedziałające.

W Androidzie 5.1 lub nowszym AudioFlinger obsługuje wstrzymywanie i wznawianie bezpośrednich (tunelowanych) wyjść audio. Jeśli HAL obsługuje wstrzymywanie i wznawianie, informacje o wstrzymaniu i wznawianiu są przekazywane do HAL.

Sekwencja wywołań wstrzymania, opróżniania i wznawiania jest przestrzegana przez wykonywanie wywołań HAL w wątku odtwarzania (tak samo jak w przypadku przenoszenia).

Wskazówki dotyczące implementacji

Interfejs HAL dźwięku

W przypadku Androida 11 identyfikator synchronizacji sprzętowej z PCR lub STC może być używany do synchronizacji obrazu i dźwięku, więc obsługiwany jest strumień zawierający tylko obraz.

W przypadku Androida 10 lub starszego urządzenia obsługujące odtwarzanie obrazu w tunelu powinny mieć co najmniej 1 profil strumienia wyjściowego audio z flagami FLAG_HW_AV_SYNCAUDIO_OUTPUT_FLAG_DIRECT w pliku audio_policy.conf. Te flagi służą do ustawiania zegara systemowego na podstawie zegara audio.

OMX

Producenci urządzeń powinni mieć osobny komponent OMX do odtwarzania wideo w tunelu (producenci mogą mieć dodatkowe komponenty OMX do innych typów odtwarzania dźwięku i wideo, takich jak odtwarzanie zabezpieczone). Komponent tunelowany:

  • W port wyjściowy tego urządzenia należy określić 0 buforów (nBufferCountMin, nBufferCountActual).

  • Zaimplementuj rozszerzenie OMX.google.android.index.prepareForAdaptivePlayback setParameter.

  • Określ jej możliwości w pliku media_codecs.xml i zadeklaruj funkcję odtwarzania w tunelu. Powinien on też zawierać informacje o ograniczeniach dotyczących rozmiaru, wyrównania lub szybkości transmisji danych. Poniżej znajdziesz przykład:

    <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>
    

Jeśli ten sam komponent OMX jest używany do obsługi dekodowania z tunelowaniem i bez tunelowania, funkcja odtwarzania z tunelowaniem powinna być opcjonalna. W takim przypadku dekodery tunelowane i nietunelowane mają te same ograniczenia. Poniżej znajdziesz przykład:

<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>

Komponowanie sprzętu (HWC)

Jeśli na ekranie jest widoczna warstwa tunelowa (warstwa z HWC_SIDEBAND compositionType), jej sidebandStream jest identyfikatorem uchwytu pasma bocznego przydzielonym przez komponent wideo OMX.

HWC synchronizuje odkodowane klatki wideo (z tunelowanego komponentu OMX) z powiązaną ścieżką audio (z identyfikatorem audio-hw-sync). Gdy nowa klatka wideo stanie się bieżąca, HWC łączy ją z bieżącą zawartością wszystkich warstw otrzymanych podczas ostatniego wywołania prepare lub set i wyświetla powstały obraz. Wywołania prepare lub set są wykonywane tylko wtedy, gdy zmieniają się inne warstwy lub właściwości warstwy pobocznej (np. pozycja lub rozmiar).

Na rysunku poniżej widać, jak HWC współpracuje z synchronizatorem sprzętowym (lub jądrem lub sterownikiem), aby połączyć klatki wideo (7b) z najnowszą kompozycją (7a) w celu wyświetlenia ich we właściwym czasie na podstawie dźwięku (7c).

HWC łączące klatki wideo na podstawie dźwięku

Rysunek 2. Synchronizator sprzętowy (lub jądro lub sterownik) HWC