Tunelowanie multimedialne

Tunelowanie multimedialne umożliwia tunelowanie skompresowanych danych wideo przez sprzętowy dekoder wideo bezpośrednio do wyświetlacza bez konieczności przetwarzania ich przez kod aplikacji czy kod platformy Androida. Kod urządzenia znajdujący się poniżej stosu Androida określa, które klatki wideo należy przesłać na wyświetlacz i kiedy, przez porównanie sygnatur czasowych 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 udostępnia na ekranie zdekodowaną klatkę wideo, która ma zostać wyrenderowana w takim samym czasie jak odpowiednia klatka audio, i pobiera historyczne instancje AudioTimestamps, aby obliczyć prawidłowy czas.

Tunelowane odtwarzanie wideo omija kod aplikacji i zmniejsza liczbę procesów obsługujących film, dlatego w zależności od implementacji OEM może zapewnić bardziej wydajne renderowanie wideo. Może też zapewnić dokładniejszą kadencję wideo i synchronizację z wybranym zegarem (PRC, STC lub audio) dzięki uniknięciu problemów czasowych spowodowanych potencjalnym odchyleniem między czasem żądania renderowania filmu przez Androida a czasem rzeczywistych vsync na sprzęcie. Tunelowanie może też jednak ograniczyć obsługę efektów GPU, takich jak rozmycie czy zaokrąglone rogi w oknach obrazu w obrazie (PIP), ponieważ bufory omijają stos grafiki 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ą na potrzeby odtwarzania, więc w większości przypadków wdrożenie wymaga tylko ponownego skonfigurowania tej biblioteki na potrzeby odtwarzania tunelowanego. Aby wdrożyć tunelowy odtwarzacz wideo na poziomie niskopoziomowym, skorzystaj z podanych niżej instrukcji.

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. Dodawaj dane audio do kolejki AudioTrack z sygnaturą czasową prezentacji dla pierwszej klatki w danych audio.

Aby odtwarzać transmisje na żywo na Androidzie w wersji 11 lub nowszej:

  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łań interfejsu API jest widoczny 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ż tunelowane odtwarzanie wideo na żądanie jest domyślnie powiązane z odtwarzaniem AudioTrack, sposób działania tunelowanego odtwarzania wideo może zależeć od sposobu działania odtwarzania dźwięku.

  • Na większości urządzeń klatka filmu jest domyślnie renderowana dopiero po rozpoczęciu odtwarzania dźwięku. Aplikacja może jednak potrzebować renderowania klatki wideo przed rozpoczęciem odtwarzania dźwięku, aby pokazać użytkownikowi bieżącą pozycję filmu podczas przewijania.

    • Aby zasygnalizować, że pierwsza klatka filmu znajdująca się w kolejce powinna zostać wyrenderowana zaraz po jej odkodowaniu, ustaw parametr PARAMETER_KEY_TUNNEL_PEEK na 1. Gdy kolejność skompresowanych klatek filmu w kolejce (na przykład są dostępne klatki B) jest zmieniana, oznacza to, że pierwsza wyświetlana klatka filmu powinna być zawsze elementem I-klatka.

    • 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 skonfigurowany, sposób działania urządzenia określa producent OEM.

  • Gdy AudioTrack nie otrzyma danych audio, a bufory są puste (niedostateczna transmisja dźwięku), odtwarzanie filmu się zatrzymuje, dopóki nie zostanie zapisanych więcej danych dźwiękowych – zegar audio przestaje być 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 dźwiękowe klatki bez dźwięku (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);
}

W trakcie tego zapytania AudioFlinger pobiera identyfikator HW_AV_SYNC z głównego urządzenia audio i wewnętrznie wiąże je 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. Obejmuje 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 komponent OMX lub Codec2 powinien zwracać uchwyt paska bocznego, którego można używać do powiązania kodeka z warstwą Hardware Composer (HWC). Gdy aplikacja wiąże powierzchnię z elementem MediaCodec, ten uchwyt paska bocznego jest przekazywany do HWC przez zasadę SurfaceFlinger, która konfiguruje warstwę jako warstwę pasma bocznego.

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 zegarzem odniesienia programu tunera, komponując bufory z bieżącą zawartością innych warstw i wyświetlając uzyskany obraz. Odbywa się to niezależnie od normalnego cyklu przygotowań i ustawień. 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óre korzysta ze struktury ConfigureVideoTunnelModeParams do przekazywania identyfikatora HW_AV_SYNC powiązanego z wyjściowym urządzeniem audio.

  • Skonfigurowanie parametru OMX_IndexConfigAndroidTunnelPeek, który informuje kodek, czy ma renderować pierwszą zdekodowaną klatkę wideo, czy nie, niezależnie od tego, czy rozpoczęło się odtwarzanie dźwięku.

  • Wysyłanie zdarzenia OMX_EventOnFirstTunnelFrameReady, gdy pierwsza tunelowana ramka wideo zostanie zdekodowana i będzie gotowa do renderowania.

Implementacja AOSP konfiguruje tryb tunelu od ACodec do OMXNodeInstance, jak 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.

Kodek2

W Androidzie 11 lub nowszym Codec2 obsługuje odtwarzanie w tunelu. Komponent dekodera powinien obsługiwać:

  • 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 do C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE w celu przydzielenia i pobrania uchwytu zakresu bocznego na potrzeby HWC.

  • Obsługa C2_PARAMKEY_TUNNEL_HOLD_RENDER po podłączeniu do C2Work, która instruuje kodek do dekodowania i sygnalizowania zakończenia pracy, ale nie do renderowania bufora wyjściowego, dopóki (1) kodek nie zostanie później poinformowany o jego wyrenderowaniu lub 2) rozpocznie się odtwarzanie dźwięku.

  • Obsługa funkcji C2_PARAMKEY_TUNNEL_START_RENDER, która instruuje kodek, aby natychmiast wyrenderował klatkę oznaczoną symbolem 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 skonfigurujesz tę funkcję, ustaw wartość false.

Implementacja AOSP konfiguruje tryb tunelu od CCodec do C2PortTunnelModeTuning, jak widać 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 przydzielić do kodeka uchwyt boczny i przekazać go z powrotem za pomocą funkcji C2PortTunnelHandlingTuning, aby HWC mógł zidentyfikować powiązany kodek.

Interfejs HAL dźwięku

W przypadku odtwarzania wideo na żądanie HAL audio otrzymuje sygnatury czasowe prezentacji audio w formacie big-endian w nagłówku, które znajduje 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 audio, 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. W celu ponownej synchronizacji podczas odtwarzania skompresowanego dźwięku może być konieczne przeanalizowanie metadanych wewnątrz skompresowanych danych audio w celu określenia czasu odtwarzania.

Wstrzymaj pomoc

Android 5 i starsze wersje nie obsługują 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 sekunda danych), wstrzymanie może wyglądać na niedziałające.

W Androidzie 5.1 i nowszych AudioFlinger obsługuje wstrzymywanie i wznawianie na bezpośrednich (tunelnych) wyjściach audio. Jeśli HAL obsługuje wstrzymanie i wznowienie, informacje o wstrzymaniu i wznowieniu ścieżki są przekazywane do HAL.

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

Sugestie 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 tunelowane odtwarzanie wideo powinny mieć co najmniej 1 profil strumienia wyjściowego audio z flagami FLAG_HW_AV_SYNC i AUDIO_OUTPUT_FLAG_DIRECT w pliku audio_policy.conf. Te flagi służą do ustawiania zegara systemowego względem zegara audio.

OMX

Producenci urządzeń powinni mieć osobny komponent OMX do tunelowanego odtwarzania wideo (na przykład mogą mieć dodatkowe komponenty OMX do obsługi innych typów odtwarzania dźwięku i obrazu, np. bezpiecznego odtwarzania). Komponent tunelowany powinien:

  • Określ 0 buforów (nBufferCountMin, nBufferCountActual) na jego porcie wyjściowym.

  • 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. Opis powinien też informować o ograniczeniach rozmiaru klatki, wyrównania i szybkości transmisji bitów. Przykład poniżej:

    <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 tunelowanego i nietunelowanego, funkcja odtwarzania tunelowanego powinna być opcjonalna. W takim przypadku dekodery tunelowane i nietunelowane mają te same ograniczenia. Oto 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>

Hardware Composer (HWC)

Gdy na wyświetlaczu znajduje się warstwa tunelowa (warstwa z atrybutem HWC_SIDEBAND compositionType), sidebandStream warstwy to uchwyt paska bocznego przydzielony przez komponent wideo OMX.

HWC synchronizuje zdekodowane klatki wideo (z tunelowanego komponentu OMX) z powiązaną ścieżką audio (z identyfikatorem audio-hw-sync). Gdy nowa klatka wideo staje się aktualna, HWC łączy ją z bieżącą zawartością wszystkich warstw otrzymanych podczas ostatniego przygotowania lub ustawienia i wyświetla wynikowy 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 ilustracji poniżej pokazujemy, jak HWC korzysta z synchronizatora sprzętowego (lub jądra lub sterownika), który łączy klatki wideo (7b) z najnowszą kompozycją (7a) w celu wyświetlenia w odpowiednim momencie na podstawie dźwięku (7c).

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

Rysunek 2. Synchronizator sprzętu HWC (lub jądra lub sterownika)