Tunelowanie multimedialne

Tunelowanie multimediów umożliwia tunelowanie skompresowanych danych wideo przez sprzętowy dekoder wideo bezpośrednio do wyświetlacza, bez przetwarzania przez kod aplikacji lub kod platformy Android. Kod specyficzny dla urządzenia znajdujący się poniżej stosu systemu Android określa, które klatki wideo należy wysłać na wyświetlacz i kiedy je wysłać, porównując znaczniki czasu prezentacji klatek wideo z jednym z następujących typów zegara wewnętrznego:

  • W przypadku odtwarzania wideo na żądanie w systemie Android 5 lub nowszym zegar AudioTrack zsynchronizowany ze znacznikami czasu prezentacji audio przesyłanymi przez aplikację

  • Do odtwarzania transmisji na żywo w systemie Android 11 lub nowszym zegar referencyjny programu (PCR) lub zegar czasu systemowego (STC) sterowany przez tuner

Tło

Tradycyjne odtwarzanie wideo na Androidzie powiadamia aplikację o zdekodowaniu skompresowanej klatki wideo. Następnie aplikacja udostępnia zdekodowaną klatkę wideo na wyświetlaczu, aby została wyrenderowana w tym samym czasie zegara systemowego, co odpowiadająca ramka audio, pobierając historyczne instancje AudioTimestamps w celu obliczenia prawidłowego taktowania.

Ponieważ tunelowane odtwarzanie wideo omija kod aplikacji i zmniejsza liczbę procesów działających na wideo, może zapewnić bardziej wydajne renderowanie wideo w zależności od implementacji OEM. Może także zapewnić dokładniejszą kadencję wideo i synchronizację z wybranym zegarem (PRC, STC lub audio), unikając problemów z synchronizacją spowodowanych potencjalnym przesunięciem pomiędzy czasem żądań Androida o renderowanie wideo a czasem rzeczywistej synchronizacji sprzętowej. Jednak tunelowanie może również ograniczyć obsługę efektów GPU, takich jak rozmycie lub zaokrąglone rogi w oknach obrazu w obrazie (PiP), ponieważ bufory omijają stos graficzny Androida.

Poniższy diagram pokazuje, jak tunelowanie upraszcza proces odtwarzania wideo.

porównanie trybu tradycyjnego i tunelowego

Rysunek 1. Porównanie tradycyjnych i tunelowanych procesów odtwarzania wideo

Dla twórców aplikacji

Ponieważ większość twórców aplikacji integruje się z biblioteką na potrzeby implementacji odtwarzania, w większości przypadków implementacja wymaga jedynie ponownej konfiguracji tej biblioteki na potrzeby odtwarzania tunelowego. W przypadku niskopoziomowej implementacji tunelowanego odtwarzacza wideo postępuj zgodnie z poniższymi instrukcjami.

W przypadku odtwarzania wideo na żądanie w systemie Android 5 lub nowszym:

  1. Utwórz instancję SurfaceView .

  2. Utwórz instancję audioSessionId .

  3. Utwórz instancje AudioTrack i MediaCodec za pomocą instancji audioSessionId utworzonej w kroku 2.

  4. Kolejkuj dane audio do AudioTrack ze znacznikiem czasu prezentacji dla pierwszej klatki audio w danych audio.

Do odtwarzania transmisji na żywo w systemie Android 11 lub nowszym:

  1. Utwórz instancję SurfaceView .

  2. Pobierz instancję avSyncHwId z Tuner .

  3. Utwórz instancje AudioTrack i MediaCodec za pomocą instancji avSyncHwId utworzonej w kroku 2.

Przepływ wywołań API jest pokazany w następujących 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);

Zachowanie podczas odtwarzania wideo na żądanie

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

  • Na większości urządzeń klatka wideo jest domyślnie renderowana dopiero po rozpoczęciu odtwarzania dźwięku. Jednak aplikacja może wymagać wyrenderowania klatki wideo przed rozpoczęciem odtwarzania dźwięku, na przykład w celu pokazania użytkownikowi bieżącej pozycji wideo podczas wyszukiwania.

    • Aby zasygnalizować, że pierwsza umieszczona w kolejce klatka wideo powinna zostać wyrenderowana zaraz po jej zdekodowaniu, ustaw parametr PARAMETER_KEY_TUNNEL_PEEK na 1 . Kiedy w kolejce zmieniona zostanie kolejność skompresowanych klatek wideo (np. gdy obecne są klatki B ), oznacza to, że pierwszą wyświetlaną klatką wideo powinna zawsze być klatka I.

    • Jeśli nie chcesz, aby pierwsza klatka wideo z kolejki była renderowana do czasu rozpoczęcia odtwarzania dźwięku, ustaw ten parametr na 0 .

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

  • Gdy dane audio nie są dostarczane do AudioTrack i bufory są puste (niedopełnienie audio), odtwarzanie wideo zatrzymuje się do czasu zapisania większej ilości danych audio, ponieważ zegar audio nie przyspiesza.

  • Podczas odtwarzania nieciągłości, których aplikacja nie może skorygować, mogą pojawić się w sygnaturach czasowych prezentacji audio. Kiedy tak się dzieje, producent OEM koryguje ujemne przerwy, zatrzymując bieżącą klatkę wideo, oraz dodatnie przerwy, usuwając klatki wideo lub wstawiając ciche klatki audio (w zależności od implementacji OEM). Położenie ramki AudioTimestamp nie zwiększa się w przypadku wstawionych cichych klatek audio.

Dla producentów urządzeń

Konfiguracja

Producenci OEM powinni stworzyć oddzielny dekoder wideo do obsługi tunelowanego odtwarzania wideo. Ten dekoder powinien informować o możliwości odtwarzania tunelowego 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 kojarzy 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, to identyfikator HW_AV_SYNC jest przekazywany do strumienia wyjściowego podczas tworzenia AudioTrack . Odbywa się to poprzez 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 wyjściowemu strumieniowi audio, czy konfiguracji Tuner , jest przekazywany do komponentu OMX lub Codec2, dzięki czemu kod OEM może powiązać kodek z odpowiednim wyjściowym strumieniem audio lub strumieniem tunera.

Podczas konfiguracji komponentu komponent OMX lub Codec2 powinien zwracać uchwyt pasma bocznego, którego można użyć do powiązania kodeka z warstwą Hardware Composer (HWC). Gdy aplikacja kojarzy powierzchnię z MediaCodec , ten uchwyt wstęgi bocznej jest przekazywany do HWC za pośrednictwem SurfaceFlinger , który konfiguruje warstwę jako warstwę wstęgi bocznej .

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 jest odpowiedzialny za odbieranie w odpowiednim czasie nowych buforów obrazu z wyjścia kodeka, zsynchronizowanych z powiązanym strumieniem wyjściowym audio lub zegarem odniesienia programu tunera, łączeniem buforów z bieżącą zawartością innych warstw i wyświetlaniem powstałego obrazu. Dzieje się to niezależnie od normalnego cyklu przygotowania i ustawienia. Wywołania przygotowania i ustawienia mają miejsce tylko wtedy, gdy ulegną zmianie inne warstwy lub gdy zmienią się właściwości warstwy bocznej (takie jak położenie lub rozmiar).

OMX

Tunelowany komponent dekodera powinien obsługiwać następujące elementy:

  • Ustawianie rozszerzonego parametru OMX.google.android.index.configureVideoTunnelMode , który wykorzystuje strukturę ConfigureVideoTunnelModeParams do przekazywania identyfikatora HW_AV_SYNC powiązanego z urządzeniem wyjściowym audio.

  • Skonfigurowanie parametru OMX_IndexConfigAndroidTunnelPeek , który informuje kodek, aby renderował lub nie renderował pierwszej zdekodowanej klatki wideo, niezależnie od tego, czy rozpoczęło się odtwarzanie dźwięku.

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

Implementacja AOSP konfiguruje tryb tunelowy w ACodec poprzez OMXNodeInstance , jak pokazano w następującym 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 przydzielić uchwyt pasma bocznego temu kodekowi 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ć bTunneled na OMX_FALSE .

Kodek2

W systemie Android 11 lub nowszym Codec2 obsługuje odtwarzanie tunelowe. Komponent dekodera powinien obsługiwać następujące elementy:

  • Konfigurowanie C2PortTunneledModeTuning , który konfiguruje tryb tunelowy i przekazuje HW_AV_SYNC pobrany z urządzenia wyjściowego audio lub konfiguracji tunera.

  • Zapytanie C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE , aby przydzielić i pobrać uchwyt pasma bocznego dla HWC.

  • Obsługa C2_PARAMKEY_TUNNEL_HOLD_RENDER po dołączeniu do C2Work , która instruuje kodek, aby dekodował i sygnalizował zakończenie pracy, ale nie renderował bufora wyjściowego, dopóki albo 1) kodek nie otrzyma później polecenia, aby go wyrenderować, albo 2) rozpocznie się odtwarzanie dźwięku.

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

  • Pozostaw debug.stagefright.ccodec_delayed_params nieskonfigurowany (zalecane). Jeśli to skonfigurujesz, ustaw na false .

Implementacja AOSP konfiguruje tryb tunelowy w CCodec poprzez C2PortTunnelModeTuning , jak pokazano w poniższym 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ć uchwyt pasma bocznego temu kodekowi i przekazać go z powrotem przez C2PortTunnelHandlingTuning , aby HWC mógł zidentyfikować powiązany kodek.

Dźwięk HAL

W przypadku odtwarzania wideo na żądanie warstwa Audio HAL odbiera znaczniki czasu prezentacji audio zgodne z danymi 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 renderowało klatki wideo zsynchronizowane z odpowiednimi ramkami audio, warstwa audio HAL powinna przeanalizować nagłówek synchronizacji i użyć znacznika czasu prezentacji w celu ponownej synchronizacji zegara odtwarzania z renderowaniem dźwięku. Aby przeprowadzić ponowną synchronizację podczas odtwarzania skompresowanego dźwięku, warstwa audio HAL może wymagać przeanalizowania metadanych znajdujących się w skompresowanych danych audio w celu określenia czasu trwania odtwarzania.

Wstrzymaj wsparcie

Android 5 lub starszy nie obsługuje wstrzymywania. Odtwarzanie tunelowane można wstrzymać tylko w przypadku braku sygnału A/V, ale jeśli wewnętrzny bufor wideo jest duży (na przykład w komponencie OMX znajduje się jedna sekunda danych), pauza wygląda na niereagującą.

W systemie Android 5.1 lub nowszym AudioFlinger obsługuje wstrzymywanie i wznawianie bezpośrednich (tunelowanych) wyjść audio. Jeśli warstwa HAL implementuje pauzę i wznawianie, pauza i wznawianie ścieżki są przekazywane do warstwy HAL.

Sekwencja wywołań pauza, opróżnianie i wznawianie jest przestrzegana poprzez wykonywanie wywołań HAL w wątku odtwarzania (tak samo jak odciążanie).

Sugestie wdrożeniowe

Dźwięk HAL

W systemie Android 11 identyfikator synchronizacji sprzętu z PCR lub STC może być używany do synchronizacji A/V, dzięki czemu obsługiwany jest tylko strumień wideo.

W przypadku Androida 10 lub starszego urządzenia obsługujące tunelowane odtwarzanie wideo powinny mieć co najmniej jeden profil wyjściowego strumienia audio z flagami FLAG_HW_AV_SYNC i AUDIO_OUTPUT_FLAG_DIRECT w pliku audio_policy.conf . Flagi te służą do ustawiania zegara systemowego na podstawie zegara audio.

OMX

Producenci urządzeń powinni mieć oddzielny komponent OMX do tunelowanego odtwarzania wideo (producenci mogą mieć dodatkowe komponenty OMX do innych typów odtwarzania audio i wideo, takich jak odtwarzanie bezpieczne). Komponent tunelowany powinien:

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

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

  • Określ jego możliwości w pliku media_codecs.xml i zadeklaruj funkcję odtwarzania tunelowanego. Powinien także wyjaśniać wszelkie ograniczenia dotyczące rozmiaru klatki, wyrównania lub szybkości transmisji. Przykład jest pokazany 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, powinien pozostawić funkcję odtwarzania tunelowanego jako niepotrzebną. Zarówno dekodery tunelowane, jak i nietunelowane mają wówczas te same ograniczenia możliwości. Przykład jest pokazany poniżej:

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

Kompozytor sprzętu (HWC)

Jeśli na wyświetlaczu znajduje się warstwa tunelowana (warstwa z compositionType HWC_SIDEBAND ), strumień sidebandStream tej warstwy jest uchwytem wstęgi bocznej przydzielonym przez komponent wideo OMX.

HWC synchronizuje zdekodowane klatki wideo (z tunelowanego komponentu OMX) z powiązaną ścieżką audio (z identyfikatorem audio-hw-sync ). Kiedy nowa klatka wideo staje się bieżąca, HWC łączy ją z bieżącą zawartością wszystkich warstw odebranych podczas ostatniego wywołania przygotowania lub ustawienia i wyświetla wynikowy obraz. Wywołania przygotowania lub ustawienia mają miejsce tylko wtedy, gdy ulegną zmianie inne warstwy lub gdy zmienią się właściwości warstwy bocznej (takie jak położenie lub rozmiar).

Poniższy rysunek przedstawia HWC współpracujący z synchronizatorem sprzętowym (lub jądrem lub sterownikiem) w celu połączenia klatek wideo (7b) z najnowszą kompozycją (7a) w celu wyświetlenia we właściwym czasie, w oparciu o dźwięk (7c).

HWC łączący klatki wideo w oparciu o dźwięk

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