2013-03-19 22 views
5

Zamierzam użyć Android MediaCodec do dekodowania strumienia wideo, a następnie użyć obrazów wyjściowych do dalszego przetwarzania obrazu w natywnym kodzie.Naruszenie zasad dostępu w natywnym kodzie za pomocą przyspieszanego sprzętowo dekodera Android MediaCodec:

Platforma: ASUS tf700t android 4.1.1. Strumień testowy: H.264 full HD @ 24 frm/s

Wewnątrz SoC Tegra-3, liczę na sprzętową obsługę dekodowania wideo. Funkcjonalnie moja aplikacja zachowuje się zgodnie z oczekiwaniami: rzeczywiście mogę uzyskać dostęp do obrazów dekodera i przetwarzać je poprawnie. Jednak mam bardzo duże obciążenie procesora dekodera.

W następujących eksperymentach obciążenie procesu/nici jest mierzone przez "wierzchołek -m 32-t" w powłoce adb. Aby uzyskać wiarygodne dane wyjściowe z "góry", wszystkie 4 rdzenie procesora są wymuszone aktywacją, uruchamiając kilka wątków w pętli na zawsze z najniższym priorytetem. Potwierdza to powtarzające się wykonywanie "cat/sys/devices/system/cpu/cpu [0-3]/online". Aby wszystko było proste, jest tylko dekodowanie wideo, brak dźwięku; i nie ma kontroli czasu, więc dekoder działa tak szybko, jak tylko może.

Pierwszy eksperyment: uruchom aplikację, wywołując funkcję przetwarzania JNI, ale wszystkie dalsze wywołania przetwarzania są komentowane. Wyniki:

  • wydajność: 25 FRM/S
  • 1% obciążenie gwintu VideoDecoder stosowania
  • 24% obciążenia gwintu Binder_3 procesowej/system/bin/Serwer mediów

ramę wydaje się, że prędkość dekodowania jest ograniczona do CPU (25% czterordzeniowego procesora) ... Po włączeniu przetwarzania wyjściowego zdekodowane obrazy są poprawne, a aplikacja działa. Jedyny problem: zbyt wysokie obciążenie procesora do dekodowania.

Po wielu eksperymentach zastanawiałem się nad wypuszczeniem MediaCodec na powierzchnię. We wszystkich innych aspektach kod jest identyczny. Wyniki:

  • przepustowości 55 FRM/s (mili !!)
  • 2% obciążenie gwintu VideoDecoder stosowania
  • 1% obciążenie gwintu MediaServer procesowej/system/bin/MediaServer

Rzeczywiście wideo jest wyświetlane na podanej powierzchni. Ponieważ nie ma prawie żadnego obciążenia procesora, to musi być sprzętowo przyspieszany ...

Wygląda na to, że de MediaCodec używa jedynie akceleracji sprzętowej, jeśli dostarczona jest powierzchnia?

Jak dotąd, tak dobrze. Byłem już skłonny używać Surface jako obejścia (nie jest to wymagane, ale w niektórych przypadkach nawet przyjemne). Ale w przypadku, gdy powierzchnia jest dostarczona, nie mam dostępu do wyjściowych obrazów! Wynik jest naruszeniem dostępu w kodzie rodzimym.

To naprawdę mnie zastanawia! Nie widziałem żadnego pojęcia o ograniczeniach dostępu ani o tym, co jest w dokumentacji: http://developer.android.com/reference/android/media/MediaCodec.html. Również nic w tym kierunku nie było wspomniane na prezentacji go/go google http://www.youtube.com/watch?v=RQws6vsoav8.

A więc: jak używać sprzętowo akcelerowanego dekodera Android MediaCodec i uzyskiwać dostęp do obrazów w natywnym kodzie?Jak uniknąć naruszenia zasad dostępu? Każda pomoc jest doceniana! Również wszelkie wyjaśnienia lub podpowiedź.

Jestem prawie pewny, że MediaExtractor i MediaCodec są używane poprawnie, ponieważ aplikacja działa poprawnie (o ile nie zapewniam powierzchni). Jest jeszcze dość eksperymentalny, a dobry projekt API jest na liście rzeczy do zrobienia ;-)

Należy pamiętać, że jedyną różnicą pomiędzy tymi dwoma eksperymentów jest zmienna mSurface: null lub rzeczywistej powierzchni w „mDecoder.configure (mediaFormat , mSurface, null, 0); "

kod inicjalizacji:

mExtractor = new MediaExtractor(); 
mExtractor.setDataSource(mPath); 

// Locate first video stream 
for (int i = 0; i < mExtractor.getTrackCount(); i++) { 
    mediaFormat = mExtractor.getTrackFormat(i); 
    String mime = mediaFormat.getString(MediaFormat.KEY_MIME); 
    Log.i(TAG, String.format("Stream %d/%d %s", i, mExtractor.getTrackCount(), mime)); 
    if (streamId == -1 && mime.startsWith("video/")) { 
     streamId = i; 
    } 
} 

if (streamId == -1) { 
    Log.e(TAG, "Can't find video info in " + mPath); 
    return; 
} 

mExtractor.selectTrack(streamId); 
mediaFormat = mExtractor.getTrackFormat(streamId); 

mDecoder = MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME)); 
mDecoder.configure(mediaFormat, mSurface, null, 0); 

width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH); 
height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT); 
Log.i(TAG, String.format("Image size: %dx%d format: %s", width, height, mediaFormat.toString())); 
JniGlue.decoutStart(width, height); 

pętla Decoder (działa w osobnym wątku):

ByteBuffer[] inputBuffers = mDecoder.getInputBuffers(); 
ByteBuffer[] outputBuffers = mDecoder.getOutputBuffers(); 

while (!isEOS && !Thread.interrupted()) { 
    int inIndex = mDecoder.dequeueInputBuffer(10000); 
    if (inIndex >= 0) { 
     // Valid buffer returned 
     int sampleSize = mExtractor.readSampleData(inputBuffers[inIndex], 0); 
     if (sampleSize < 0) { 
      Log.i(TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM"); 
      mDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); 
      isEOS = true; 
     } else { 
      mDecoder.queueInputBuffer(inIndex, 0, sampleSize, mExtractor.getSampleTime(), 0); 
      mExtractor.advance(); 
     } 
    } 

    int outIndex = mDecoder.dequeueOutputBuffer(info, 10000); 
    if (outIndex >= 0) { 
     // Valid buffer returned 
     ByteBuffer buffer = outputBuffers[outIndex]; 
     JniGlue.decoutFrame(buffer, info.offset, info.size); 
     mDecoder.releaseOutputBuffer(outIndex, true); 
    } else { 
     // Some INFO_* value returned 
     switch (outIndex) { 
     case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: 
      Log.i(TAG, "RunDecoder: INFO_OUTPUT_BUFFERS_CHANGED"); 
      outputBuffers = mDecoder.getOutputBuffers(); 
      break; 
     case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: 
      Log.i(TAG, "RunDecoder: New format " + mDecoder.getOutputFormat()); 
      break; 
     case MediaCodec.INFO_TRY_AGAIN_LATER: 
      // Timeout - simply ignore 
      break; 
     default: 
      // Some other value, simply ignore 
      break; 
     } 
    } 

    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 
     Log.d(TAG, "RunDecoder: OutputBuffer BUFFER_FLAG_END_OF_STREAM"); 
     isEOS = true; 
    } 
} 
+0

Nadal nie ma rozwiązania. Wszelkie sugestie są nadal mile widziane. Również sugestie dotyczące eksperymentów w celu zwiększenia zrozumienia. Czy ktoś ma dekodowanie sprzętowe działające z wykorzystaniem MediaCodec? Może na innej platformie? – Bram

+0

Bram, próbuję rozwiązać dokładnie ten sam problem. Wygląda na to, że to spowolnienie nie dotyczy wielu kopii zdekodowanego bufora.Kiedy zdekodowane dane są przeznaczone do prezentacji na natywnej powierzchni, wygląda na to, że istnieje pewna bezpośrednia ścieżka danych i wykorzystuje ona TILER (rendering kaflowy). Gdy potrzebujesz dostępu do pełnej ramki YUV (np. Chcesz uzyskać dostęp do zdekodowanego bufora), dekoder musi wykonać dodatkowe zadania, takie jak renderowanie wszystkich danych do bufora pamięci i kopiowanie go, co czyni go tak wolnym. Dosłownie zmarnowałem tydzień mojego życia, próbując naprawić problem, ale wydaje się, że nie ma nic do naprawienia. – Pavel

+0

Co więcej, w moim przypadku miałem 720p przy 30fps, którego nie byłem w stanie odcyfrować w czasie rzeczywistym, podczas gdy odtwarzacz natywny nie miał problemów z opłaceniem go. – Pavel

Odpowiedz

3

Jeśli skonfigurować powierzchni wyjściowej, dekodowany dane są zapisywane do bufora graficznego, które mogą być wykorzystane jako tekstury OpenGL ES (poprzez zewnętrzny „tekstury” rozszerzenie). Różne elementy sprzętu przechwytują dane w formacie, który im się podoba, a procesor nie musi kopiować danych.

Jeśli nie skonfigurujesz powierzchni, wyjście przechodzi w java.nio.ByteBuffer. Istnieje co najmniej jedna kopia bufora, aby pobrać dane z bufora przydzielonego przez MediaCodec do twojego ByteByffer i prawdopodobnie inną kopię, aby odzyskać dane z twojego kodu JNI. Oczekuję, że to, co widzisz, to koszty ogólne, a nie koszt dekodowania oprogramowania.

Ty może móc poprawić sytuację wysyłając wyjście do SurfaceTexture, rozdzierające się na FBO lub pbuffer, a następnie za pomocą glReadPixels wyodrębnić dane. Jeśli przeczytasz "bezpośredni" ByteBuffer lub zadzwonisz pod numer glReadPixels z kodu natywnego, zredukujesz narzut JNI. Wadą tego podejścia jest to, że twoje dane będą w RGB zamiast YCbCr. (OTOH, jeśli pożądane transformacje można wyrazić w module cieniującym GLES 2.0, można uzyskać procesor GPU, który wykona pracę zamiast CPU.)

Jak zauważono w innej odpowiedzi, dekodery na różnych urządzeniach wyprowadzają dane ByteBuffer w różnych formatach, więc interpretacja danych w oprogramowaniu może nie być możliwa, jeśli przenośność jest dla Ciebie ważna.

Edytuj:Grafika ma teraz przykład użycia procesora GPU do przetwarzania obrazu. Możesz zobaczyć film demonstracyjny here.

+0

Dzięki! Tak więc oświadczasz przy pomocy @fadden, że korzystasz z powierzchni, z której wykluczamy się wzajemnie, aby uzyskać dostęp do ByteBuffer. Brakujący dokument w [dequeueOutputBuffer] (http://developer.android.com/reference/android/media/MediaCodec.html#dequeueOutputBuffer%28android.media.MediaCodec.BufferInfo,%20long%29)?!?! Cóż, dla mnie potrzebuję YCbCr w natywnym kodzie, więc przejście do GLES nie pomaga. Ponadto powierzchnia była tylko do testowania. O kopiowaniu danych? Czy to zajmie 40ms (25 frm/s)? W moim macierzystym kodzie mogę to zrobić w 11 ms. A więc wciąż zbyt dużo obciążenia procesora. Dobrze? Btw. przenośność nie jest moją główną troską. – Bram

+1

Dokumenty "MediaCodec" mogłyby być lepsze. Trudno wiedzieć, gdzie cały czas się dzieje, nie widząc wszystkiego, co robi urządzenie - wszystkie są trochę inne. Możliwe, że "natywny" format używany przez Surface jest czymś szalonym i transkoduje go do prostszego YUV w drodze do ByteBuffer. Przechodzenie z 25 klatek na sekundę na 55 klatek na sekundę to różnica 40-18 = 22 ms - dwie z kopii bufora. Czasami na wyjściu logcat widać, że otwiera (lub nie) sprzętowe urządzenie dekodujące. W każdym razie nie widzę niczego złego w tym, co robisz. – fadden

+0

Sprawdzono dane wyjściowe logcat. Nie wykazuje żadnej różnicy podczas pracy z powierzchnią w porównaniu z bez powierzchni (podczas gdy w rzeczywistości niektóre informacje są wyświetlane podczas uruchamiania i zatrzymywania dekodera). Potwierdza to twoje oświadczenie: nasza aplikacja nie cierpi na dekodowanie w oprogramowaniu, a nie narzut. To zamyka bieżący temat. Następnym tematem będzie oczywiście: jak zmniejszyć to obciążenie? Jeśli sam android może wyświetlać obrazy na wyświetlaczu z obciążeniem prawie 0 cpu, w jaki sposób mogę je pobrać w mojej aplikacji z obciążeniem prawie 0 cpu? – Bram

0

używam mediacodec api na Nexus 4 i uzyskać format koloru wyjściowej QOMX_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka. Myślę, że ten format jest rodzajem formatu sprzętu i może być renderowany tylko przez renderowanie sprzętu. Interesujące jest to, że kiedy używam zerowej i rzeczywistej powierzchni do skonfigurowania powierzchni dla MediaCodec, długość bufora wyjściowego zmieni się odpowiednio na wartość rzeczywistą i 0. Nie wiem dlaczego. Myślę, że możesz zrobić kilka eksperymentów na różnych urządzeniach, by uzyskać więcej wyników. O sprzęcie przyspieszenie widać http://www.saschahlusiak.de/2012/10/hardware-acceleration-on-sgs2-with-android-4-0/

+0

Dzięki za podpowiedź w różnych formatach kolorów. Bez powierzchni mój 'mDecoder.getOutputFormat(). GetInteger (" format koloru ")' wynosi 19 (COLOR_FormatYUV420Planar w [link] (http://developer.android.com/reference/android/media/MediaCodecInfo.CodecCapabilities.html)). Z powierzchnią to 256. Nie mam pojęcia, co to oznacza ... Poza faktyczną Powierzchnią, info.size staje się 0. Oczywiście, nie powinienem próbować czytać bufora o rozmiarze 0 w moim JniGlue.decoutFrame(). To może wyjaśnić awarię. Ale nadal ... zapewnienie powierzchni było tylko obejściem, aby dekodowanie sprzętowe działało ... – Bram

+0

Z wyjściem na powierzchnię, nie dostajesz żadnych danych w 'ByteBuffer'. Nadal otrzymujesz indeks z 'dequeueOutputBuffer()', dzięki czemu możesz otrzymać powiadomienie, że ramka jest dostępna, i wybrać czy renderować ją z 'render' arg do' releaseOutputBuffer() '. Nie możesz dotknąć rzeczywistych bitów bez renderowania powierzchni. – fadden

+0

Niezależnie od tego, czy ustawiłem dekoder.configure (format, null, null, 0); OR decoder.configure (format, powierzchnia, null, 0); Format koloru jest taki sam - COLOR_QCOM_FormatYUV420SemiPlanar - Stała wartość: 2141391872 (0x7fa30c00) dla Nexusa 7 i COLOR_TI_FormatYUV420PackedSemiPlanar - Stała wartość: 2130706688 (0x7f000100) dla Galaxy Nexus. Czemu? – Harkish