2015-09-29 5 views
0

Я декодирование h264 видеопотока со следующим кодом (original guide):MediaCodec аварии на высокое качество потока

public void configure(Surface surface, int width, int height, ByteBuffer csd0) { 
     String VIDEO_FORMAT = "video/avc"; 
     if (mConfigured) { 
      throw new IllegalStateException("Decoder is already configured"); 
     } 
     MediaFormat format = MediaFormat.createVideoFormat(VIDEO_FORMAT, width, height); 
     // little tricky here, csd-0 is required in order to configure the codec properly 
     // it is basically the first sample from encoder with flag: BUFFER_FLAG_CODEC_CONFIG 
     format.setByteBuffer("csd-0", csd0); 
     try { 
      mCodec = MediaCodec.createDecoderByType(VIDEO_FORMAT); 
     } catch (IOException e) { 
      throw new RuntimeException("Failed to create codec", e); 
     } 
     mCodec.configure(format, surface, null, 0); 
     mCodec.start(); 
     mConfigured = true; 
    } 

    @SuppressWarnings("deprecation") 
    public void decodeSample(byte[] data, int offset, int size, long presentationTimeUs, int flags) { 
     if (mConfigured && mRunning) { 
      int index = mCodec.dequeueInputBuffer(mTimeoutUs); 
      if (index >= 0) { 
       ByteBuffer buffer; 
       // since API 21 we have new API to use 
       if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 
        buffer = mCodec.getInputBuffers()[index]; 
        buffer.clear(); 
       } else { 
        buffer = mCodec.getInputBuffer(index); 
       } 
       if (buffer != null) { 
        buffer.put(data, offset, size); 
        mCodec.queueInputBuffer(index, 0, size, presentationTimeUs, flags); 
       } 
      } 
     } 
    } 

    @Override 
    public void run() { 
     try { 
      MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); 
      while (mRunning) { 
       if (mConfigured) { 
        int index = mCodec.dequeueOutputBuffer(info, mTimeoutUs); 
        if (index >= 0) { 
         // setting true is telling system to render frame onto Surface 
         mCodec.releaseOutputBuffer(index, true); 
         if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == MediaCodec.BUFFER_FLAG_END_OF_STREAM) { 
          break; 
         } 
        } 
       } else { 
        // just waiting to be configured, then decode and render 
        try { 
         Thread.sleep(10); 
        } catch (InterruptedException ignore) { 
        } 
       } 
      } 
     } finally { 
      if (mConfigured) { 
       mCodec.stop(); 
       mCodec.release(); 
      } 
     } 
    } 

Я могу запустить это как на моем Nexus 6 (API 22) и Samsung ядра галактики (апи 16) на низком и среднем качестве. Однако, когда я переключаюсь на высокое качество (720p), он падает на Samsung примерно через 30 кадров (но ничего не отображается на экране).

E/ACodec﹕ [OMX.qcom.video.decoder.avc] ERROR(0x8000100a) 
E/MediaCodec﹕ Codec reported an error. (omx error 0x8000100a, internalError -2147483648) 
[...] 
W/System.err﹕ java.lang.IllegalStateException 
W/System.err﹕ at android.media.MediaCodec.dequeueInputBuffer(Native Method) 
W/System.err﹕ at com.test.stream.VideoDecoder$Worker.decodeSample(VideoDecoder.java:95) 
W/System.err﹕ at com.test.stream.VideoDecoder.decodeSample(VideoDecoder.java:24) 
W/System.err﹕ at com.test.stream.VideoThread.run(VideoThread.java:160) 

Ошибка выше, является первой ошибкой, которая появляется, то IllegalStateException будет потом выброшены на каждом кадре.

Мой вопрос в том, является ли это проблемой, специфичной для устройства (из-за: более раннего api/устройства, менее мощного и т. Д.) Или что-то действительно не так? и как мне с этим бороться?

ответ

2

Для моего декодера h.264 для Android я немного отличаюсь от вашей настройки. Я думаю, что вы используете более современный уровень api. Но для меня это выглядит примерно так:

public void startDecoder() { 
    // Initilize codec 
    mediaCodec = MediaCodec.createDecoderByType("video/avc"); 
    mediaFormat = MediaFormat.createVideoFormat("video/avc", 0, 0); 
    bufferInfo = new MediaCodec.BufferInfo(); 

    // STOPS unit-tests from crashing here from mocked out android 
    if (mediaCodec != null) { 
     mediaCodec.configure(mediaFormat, targetSurface, null, 0); 
     mediaCodec.start(); 
     decoderThread = new Thread(this); 
     decoderThread.start(); 
    } 
} 

// Decoder Thread относится к этому классу, который делает декодер/визуализации цикла:

public void run() { 
    //mediaCodec input + output dequeue timeouts 
    long kInputBufferTimeoutMs = 50; 
    long kOutputBufferTimeoutMs = 50; 

    while (running && mediaCodec != null) { 
     synchronized (mediaCodec) { 
      // stop if not running. 
      if (!running || mediaCodec == null) 
       break; 

      // Only push in new data if there is data available in the queue 
      if (naluSegmentQueue.size() > 0) { 
       int inputBufferIndex = mediaCodec.dequeueInputBuffer(kInputBufferTimeoutMs); 
       if (inputBufferIndex >= 0) { 
        NaluSegment segment = naluSegmentQueue.poll(); 
        codecInputBufferAvailable(segment, mediaCodec, inputBufferIndex); 
       } 
      } 

      // always check if output is available. 
      int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, kOutputBufferTimeoutMs); 
      if (outputBufferIndex >= 0) { 
       // Try and render first 
       codecOuputBufferAvailable(mediaCodec, outputBufferIndex, bufferInfo); 
      } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 
       // Subsequent data will conform to new format. 
       // Can ignore if using getOutputFormat(outputBufferId) 
       mediaFormat = mediaCodec.getOutputFormat(); 
      } 
     } 
    } 
} 

Чтобы поместить данные в декодер, включая параметры. Я не беспокоюсь о попытке использовать сетевые потоки csd-0/1, может иметь изменение формата описания, и его проще просто позволить ему подбираться динамически.

private void codecInputBufferAvailable(NaluSegment segment, MediaCodec codec, int index) { 
    int flags = (segment.getType() == NaluType.SPS 
      || segment.getType() == NaluType.PPS 
      || segment.getType() == NaluType.SUPP_ENHANCEMENT) ? 
      MediaCodec.BUFFER_FLAG_CODEC_CONFIG : MediaCodec.BUFFER_FLAG_SYNC_FRAME; 

    ByteBuffer[] buffers = codec.getInputBuffers(); 
    ByteBuffer buffer = buffers[index]; 
    // Can throw buffer overflow exception when buffer sizes are too small. 
    try { 
     buffer.put(segment.getBuffer()); 
     codec.queueInputBuffer(index, 0, segment.getBufferSize(), 0, flags); 
    } catch(Exception e) { 
     Log.e(TAG, "Failed to push buffer to decoder"); 
    } 
} 

ВНИМАНИЕ: buffer.put (segment.getBuffer()); getBuffer() здесь всегда возвращает 4 байта applicationb буфер. Андроидные декодеры не понимают 3 байта. Итак, если у вас есть 3-байтовый блок, включите его в 4 байта магической последовательности с длиной + 1 и 0x00, 0x00, 0x00, 0x01 в качестве начальной магической последовательности остальная часть буфера должна быть buffer [headerLength].

Обратите внимание, что здесь не указано предупреждение компилятора, но он может вызывать буфер исключение переполнения здесь, если у вас очень большая полезная нагрузка, а байт-буфер слишком мал.

До тех пор, пока вы правильно разобрали свои блоки NAL, это должно сработать для вас. Но для моего случая я заметил, что единицы NAL могут быть 3 или 4 байта для магического заголовка.

/** 
* H264 is comprised of NALU segments. 
* 
* XXXX Y ZZZZZZZZ -> XXXX Y ZZZZZZZZ -> XXXX Y ZZZZZZZZ 
* 
* Each segment is comprised of: 
* 
* XXXX -> Magic byte header (0x00, 0x00, 0x00, 0x01) NOTE: this can be either 3 of 4 bytes 
* Y  -> The Nalu Type 
* ZZZ... -> The Payload 
* 
* Notice there is no nalu length specified. To parse an nalu, you must 
* read until the next magic-byte-sequence AKA the next segment to figure 
* out the full nalu length 
**/ 
public static List<NaluSegment> parseNaluSegments(byte[] buffer) throws NaluBufferException { 
    List<NaluSegment> segmentList = new ArrayList<>(); 
    if (buffer.length < 6) { 
     return segmentList; 
    } 

    int lastStartingOffset = -1; 
    for (int i = 0; i < buffer.length - 10; ++i) { 
     **if (buffer[i] == 0x00 && buffer[i+1] == 0x00 && buffer[i+2] == 0x01)** { 
      int naluType = (buffer[i+3] & 0x1F); 
      NaluSegment segment = new NaluSegment(naluType, 3, i); 

      **if (i > 0 && buffer[i-1] == 0x00)** { 
       // This is actually a 4 byte segment 
       int currentSegmentOffset = segment.getOffset(); 
       segment.setHeaderSize(4); 
       segment.setOffset(currentSegmentOffset - 1); 
      } 
... 

Создайте свои собственные объекты сегмента nalu и не забывайте о завершающем NAL.

Надеюсь, это поможет.

+1

Спасибо за ваш обширный ответ. Вы сталкивались с какими-либо ограничениями, такими как те, о которых я упоминал, с этим решением? – sadhi

+0

Если вы собираетесь адаптивно-потоковой передачи и переключите один и тот же медиакодек на более качественное видео, вам понадобится: media-codec.stop() и перенастроить с новым разрешением, или я думаю, что вы можете использовать: KeyMaxWidth и KeyMaxHeight в медиа-формате, чтобы обеспечить поддержку более высокого качества «на лету». – redbrain