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.
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:
Utwórz instancję
SurfaceView
.Utwórz instancję
audioSessionId
.Utwórz instancje
AudioTrack
iMediaCodec
z instancjąaudioSessionId
utworzoną w kroku 2.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:
Utwórz instancję
SurfaceView
.Pobierz instancję
avSyncHwId
zTuner
.Utwórz instancje
AudioTrack
iMediaCodec
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
na1
. 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.configureVideoTunnelMode
extendedConfigureVideoTunnelModeParams
, który używa strukturyConfigureVideoTunnelModeParams
do przekazywania identyfikatoraHW_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 przekazujeHW_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 doC2Work
, 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 jakoC2_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, ¶ms);
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_SYNC
i AUDIO_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).
Rysunek 2. Synchronizator sprzętowy HWC (lub jądro lub sterownik)