Tunelowanie multimediów

Tunelowanie multimediów umożliwia przesyłanie skompresowanych danych wideo przez dekoder sprzętowy bezpośrednio na wyświetlacz bez przetwarzania przez kod aplikacji lub kod platformy Android. Kod urządzenia znajdujący się poniżej stosu Androida określa, które klatki wideo mają być wysyłane na wyświetlacz i kiedy mają być wysyłane. Robi to, porównując sygnatury czasowe prezentacji klatek wideo z jednym z tych typów zegara wewnętrznego:

  • W przypadku odtwarzania wideo na żądanie na urządzeniach z Androidem w wersji 5 lub nowszej aplikacja przekazuje AudioTrack zegar zsynchronizowany z sygnaturami czasowymi prezentacji audio.

  • W przypadku odtwarzania transmisji na żywo na urządzeniach z Androidem 11 lub nowszym wymagany jest zegar referencyjny programu (PCR) lub zegar systemowy (STC) sterowany przez tuner.

Tło

Tradycyjne odtwarzanie wideo na Androidzie powiadamia aplikację o zdekodowaniu skompresowanej klatki wideo. Aplikacja zwalnia dekodowaną klatkę wideo, aby wyświetlić ją w tym samym czasie zegara systemowego co odpowiednia klatka audio, pobierając historyczne instancje AudioTimestamps, aby obliczyć prawidłowy czas.

Odtwarzanie tunelowane omija kod aplikacji i zmniejsza liczbę procesów działających na film, dzięki czemu może zapewniać bardziej wydajne renderowanie wideo w zależności od implementacji OEM. Pozwala też zapewnić dokładniejszą częstotliwość i synchronizację wideo z wybranym zegarem (PRC, STC lub audio), unikając problemów z synchronizacją wynikających z potencjalnego odchylenia między czasem żądań Androida dotyczących renderowania wideo a czasem rzeczywistych synchronizacji pionowych sprzętu. Tunelowanie może jednak ograniczyć obsługę efektów GPU, takich jak rozmycie czy zaokrąglone rogi w oknach obrazu w obrazie, ponieważ bufory omijają stos graficzny Androida.

Ten diagram pokazuje, jak tunelowanie upraszcza proces odtwarzania wideo.

porównanie trybów tradycyjnego i tunelowego,

Rysunek 1. Porównanie tradycyjnego i tunelowanego procesu odtwarzania wideo

Dla deweloperów aplikacji

Większość deweloperów aplikacji integruje się z biblioteką w celu wdrożenia odtwarzania, więc w większości przypadków wdrożenie wymaga tylko ponownej konfiguracji tej biblioteki pod kątem odtwarzania tunelowanego. Aby wdrożyć odtwarzacz wideo z tunelowaniem na niskim poziomie, postępuj zgodnie z tymi instrukcjami.

W przypadku odtwarzania filmów na żądanie na urządzeniach z Androidem 5 lub nowszym:

  1. Utwórz instancję SurfaceView.

  2. Utwórz instancję audioSessionId.

  3. Utwórz instancje AudioTrackMediaCodec z instancją audioSessionId utworzoną w kroku 2.

  4. Umieść w kolejce dane audio do AudioTrack z sygnaturą czasową prezentacji dla pierwszej klatki audio w danych audio.

W przypadku odtwarzania transmisji na żywo na urządzeniach z Androidem 11 lub nowszym:

  1. Utwórz instancję SurfaceView.

  2. Pobierz instancję avSyncHwIdTuner.

  3. Utwórz instancje AudioTrackMediaCodec z instancją avSyncHwId utworzoną w kroku 2.

Przepływ wywołań interfejsu API pokazują te fragmenty 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 wideo na żądanie

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

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

    • Aby zasygnalizować, że pierwsza klatka filmu w kolejce powinna zostać wyrenderowana natychmiast po zdekodowaniu, ustaw parametr PARAMETER_KEY_TUNNEL_PEEK na 1. Gdy skompresowane klatki wideo są ponownie porządkowane w kolejce (np. gdy występują klatki B), oznacza to, że pierwsza wyświetlana klatka wideo powinna być zawsze klatką I.

    • Jeśli nie chcesz, aby pierwsza klatka filmu w kolejce była renderowana do momentu rozpoczęcia odtwarzania dźwięku, ustaw ten parametr na 0.

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

  • Gdy dane audio nie są dostarczane do AudioTrack, a bufory są puste (niedobór dźwięku), odtwarzanie wideo zatrzymuje się, 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 takiej sytuacji producent OEM koryguje ujemne luki, wstrzymując bieżącą klatkę wideo, a dodatnie luki, usuwając klatki wideo lub wstawiając ciche klatki audio (w zależności od implementacji producenta OEM). Pozycja ramki AudioTimestamp nie zwiększa się w przypadku wstawionych ramek audio z ciszą.

Dla producentów urządzeń

Konfiguracja

Producenci OEM powinni utworzyć osobny dekoder wideo, aby obsługiwać odtwarzanie tunelowane. Dekoder powinien informować o możliwości odtwarzania tunelowanego w pliku media_codecs.xml:

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

Gdy instancja tunelowana MediaCodec jest skonfigurowana z identyfikatorem sesji audio, wysyła do AudioFlinger zapytanie 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 zapytaniaAudioFlinger pobiera HW_AV_SYNC identyfikator z podstawowego 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, identyfikator HW_AV_SYNC jest przekazywany do strumienia wyjściowego podczas tworzenia AudioTrack. Odbywa się to w wątku 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 OMX lub Codec2 powinien zwrócić uchwyt pasma bocznego, którego można użyć do powiązania kodeka z warstwą kompozytora sprzętowego (HWC). Gdy aplikacja powiąże powierzchnię z MediaCodec, ten uchwyt sygnału pomocniczego zostanie przekazany do HWC za pomocą SurfaceFlinger, które skonfiguruje warstwę jako warstwę sygnału pomocniczego.

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 odbieranie nowych buforów obrazu z wyjścia kodeka w odpowiednim czasie, zsynchronizowanym z powiązanym strumieniem wyjściowym audio lub zegarem referencyjnym programu tunera, łączenie buforów z bieżącą zawartością innych warstw i wyświetlanie wynikowego obrazu. Dzieje się to niezależnie od normalnego cyklu przygotowywania i ustawiania. Wywołania prepare i set są wykonywane tylko wtedy, gdy zmieniają się inne warstwy lub właściwości warstwy pasma bocznego (np. pozycja lub rozmiar).

OMX

Komponent dekodera tunelowanego powinien obsługiwać te funkcje:

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

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

  • Wysyłanie zdarzenia OMX_EventOnFirstTunnelFrameReady, gdy pierwsza klatka tunelowanego filmu zostanie zdekodowana i będzie gotowa do wyrenderowania.

Implementacja AOSP konfiguruje tryb tunelowania w ACodec za pomocą 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ć do tego kodeka uchwyt pasma bocznego i przekazać go z powrotem za pomocą elementu 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 Androidzie 11 i nowszych Codec2 obsługuje odtwarzanie tunelowe. Dekoder powinien obsługiwać te funkcje:

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

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

  • Obsługa C2_PARAMKEY_TUNNEL_HOLD_RENDER po dołączeniu do C2Work, co nakazuje kodekowi dekodowanie i sygnalizowanie zakończenia pracy, ale nie renderowanie bufora wyjściowego, dopóki 1) kodek nie otrzyma później polecenia renderowania lub 2) nie rozpocznie się odtwarzanie dźwięku.

  • Obsługa C2_PARAMKEY_TUNNEL_START_RENDER, która nakazuje kodekowi natychmiastowe renderowanie klatki oznaczonej jako C2_PARAMKEY_TUNNEL_HOLD_RENDER, nawet jeśli odtwarzanie dźwięku się nie rozpoczęło.

  • Pozostaw debug.stagefright.ccodec_delayed_params bez konfiguracji (zalecane). Jeśli skonfigurujesz to ustawienie, ustaw wartość 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 przydzielić kodekowi uchwyt pasma bocznego i przekazać go z powrotem za pomocą funkcji C2PortTunnelHandlingTuning, aby HWC mógł zidentyfikować powiązany kodek.

HAL dźwięku

W przypadku odtwarzania wideo na żądanie interfejs HAL audio otrzymuje sygnatury czasowe prezentacji audio w formacie big-endian w nagłówku znajdującym się na początku każdego bloku danych audio zapisywanego 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ł klatki wideo w synchronizacji z odpowiednimi klatkami audio, warstwa HAL audio powinna analizować nagłówek synchronizacji i używać sygnatury czasowej prezentacji do ponownej synchronizacji zegara odtwarzania z renderowaniem dźwięku. Aby ponownie zsynchronizować odtwarzanie skompresowanego dźwięku, warstwa HAL dźwięku może potrzebować przeanalizowania metadanych w danych skompresowanego dźwięku, aby określić czas odtwarzania.

Wstrzymanie pomocy

Android 5 lub starszy nie obsługuje wstrzymywania. Odtwarzanie tunelowane można wstrzymać tylko w przypadku braku danych audio lub wideo, ale jeśli wewnętrzny bufor wideo jest duży (np. w komponencie OMX jest 1 sekunda danych), wstrzymanie wydaje się nie reagować.

W Androidzie 5.1 lub nowszym funkcja AudioFlinger obsługuje wstrzymywanie i wznawianie bezpośrednich (tunelowanych) wyjść audio. Jeśli HAL obsługuje wstrzymywanie i wznawianie, polecenia wstrzymania i wznawiania są przekazywane do HAL.

Sekwencja wywołań wstrzymania, opróżnienia i wznowienia jest uwzględniana przez wykonanie wywołań HAL w wątku odtwarzania (tak samo jak w przypadku odciążenia).

Sugestie dotyczące wdrożenia

HAL dźwięku

W przypadku Androida 11 do synchronizacji A/V można używać identyfikatora synchronizacji sprzętowej z PCR lub STC, więc obsługiwany jest strumień tylko wideo.

W przypadku Androida 10 lub starszego urządzenia obsługujące tunelowane odtwarzanie wideo powinny mieć co najmniej jeden 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 tunelowanego wideo (mogą mieć dodatkowe komponenty OMX do innych typów odtwarzania audio i wideo, np. bezpiecznego odtwarzania). 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. Powinny też być w nim wyjaśnione wszelkie ograniczenia dotyczące rozmiaru klatki, wyrównania lub szybkości transmisji bitów. Przykład znajdziesz 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 niewymaganą. Dekodery tunelowane i nietunelowane mają wtedy takie same ograniczenia. Przykład znajdziesz 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>

Hardware Composer (HWC)

Gdy na wyświetlaczu znajduje się warstwa tunelowana (warstwa z ikoną HWC_SIDEBAND compositionType), jej sidebandStream jest uchwytem pasma bocznego przydzielonym przez komponent wideo OMX.

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

Na poniższym rysunku przedstawiono HWC współpracujący z synchronizatorem sprzętowym (lub jądrem lub sterownikiem), aby łączyć klatki wideo (7b) z najnowszą kompozycją (7a) i wyświetlać je w odpowiednim czasie na podstawie dźwięku (7c).

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

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