Android MediaCodec API — музыка воспроизводится на эмуляторе, а не на устройстве

Приведенный ниже код отлично работает на эмуляторе, но не на устройстве. Я нашел следующие строки, которые показались мне подозрительными:

V/MediaExtractor(5030): автоматическое обнаружение мультимедийного контента как «аудио/mpeg» с уверенностью 0,20 В/ChromiumHTTPDataSource(5030): mContentSize не определен или сеть может быть отключена V/ChromiumHTTPDataSource(5030): mContentSize не определена или сеть может быть отключена D /com.example.mediacodectest(5030): ТИП MIME: аудио/mpeg

Жду советов/предложений. Заранее спасибо...

private class PlayerThread extends Thread {

    @Override
    public void run() {
        MediaExtractor extractor;
        MediaCodec codec;
        ByteBuffer[] codecInputBuffers;
        ByteBuffer[] codecOutputBuffers;

        AudioTrack mAudioTrack;

        mAudioTrack = new AudioTrack(
                AudioManager.STREAM_MUSIC, 
                44100, 
                AudioFormat.CHANNEL_OUT_STEREO, 
                AudioFormat.ENCODING_PCM_16BIT,
                8192 * 2, 
                AudioTrack.MODE_STREAM);

        extractor = new MediaExtractor();
        try 
        {
            extractor.setDataSource("http://anmp3streamingsource.com/stream");
            MediaFormat format = extractor.getTrackFormat(0);
            String mime = format.getString(MediaFormat.KEY_MIME);
            Log.d(TAG, String.format("MIME TYPE: %s", mime));

            codec = MediaCodec.createDecoderByType(mime);
            codec.configure(
                    format, 
                    null /* surface */, 
                    null /* crypto */, 
                    0 /* flags */ );
            codec.start();
            codecInputBuffers = codec.getInputBuffers();
            codecOutputBuffers = codec.getOutputBuffers();

            extractor.selectTrack(0); // <= You must select a track. You will read samples from the media from this track!

            boolean sawInputEOS = false;
            boolean sawOutputEOS = false;               

            for (;;) {
                int inputBufIndex = codec.dequeueInputBuffer(-1);
                if (inputBufIndex >= 0) {
                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];

                    int sampleSize = extractor.readSampleData(dstBuf, 0);
                    long presentationTimeUs = 0;
                    if (sampleSize < 0) {
                        sawInputEOS = true;
                        sampleSize = 0;
                    } else {
                        presentationTimeUs = extractor.getSampleTime();
                    }

                    codec.queueInputBuffer(inputBufIndex,
                                           0, //offset
                                           sampleSize,
                                           presentationTimeUs,
                                           sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
                    if (!sawInputEOS) {
                        extractor.advance();
                    }

                    MediaCodec.BufferInfo info = new BufferInfo();
                    final int res = codec.dequeueOutputBuffer(info, -1);
                    if (res >= 0) {
                        int outputBufIndex = res;
                        ByteBuffer buf = codecOutputBuffers[outputBufIndex];

                        final byte[] chunk = new byte[info.size];
                        buf.get(chunk); // Read the buffer all at once
                        buf.clear(); // ** MUST DO!!! OTHERWISE THE NEXT TIME YOU GET THIS SAME BUFFER BAD THINGS WILL HAPPEN

                        mAudioTrack.play();

                        if (chunk.length > 0) {
                            mAudioTrack.write(chunk, 0, chunk.length);
                        }
                        codec.releaseOutputBuffer(outputBufIndex, false /* render */);

                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                            sawOutputEOS = true;
                        }
                    } 
                    else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) 
                    {
                        codecOutputBuffers = codec.getOutputBuffers();
                    } 
                    else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) 
                    {
                        final MediaFormat oformat = codec.getOutputFormat();
                        Log.d(TAG, "Output format has changed to " + oformat);
                        mAudioTrack.setPlaybackRate(oformat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
                    }
                 }
            }

        }                
        catch (IOException e) 
        {
            Log.e(TAG, e.getMessage());
        }
    }
}

person burakk    schedule 18.02.2014    source источник
comment
Ваша обработка вывода не совсем правильная; Я добавил пункт №11 часто задаваемых вопросов по адресу bigflake.com/mediacodec/#q11 . Строки журнала, которые вы показываете, не кажутся слишком подозрительными. Можете ли вы более четко определить, почему код не работает на устройстве?   -  person fadden    schedule 19.02.2014
comment
Спасибо за ответ. Код отлично работает на эмуляторе уровня API 17, но не на реальном устройстве с уровнем API 18. Я мог отлаживать до строки: final int res = codec.dequeueOutputBuffer(info, -1); но не дальше...   -  person burakk    schedule 19.02.2014


Ответы (1)


Я не работал со звуком, но я думаю, что могу увидеть проблему. Вы зависли в dequeueOutputBuffer(), потому что кодек ожидает ввода данных.

Некоторым видеокодекам требуется ~ 4 буфера ввода, прежде чем они даже закончат инициализацию (например). Я ожидаю, что некоторые аудиокодеки могут вести себя так же. Реализации кодеков варьируются от устройства к устройству, поэтому неудивительно, что то, что работает на эмуляторе, ведет себя совершенно по-разному.

Измените таймауты с -1 (ждать навсегда) на что-нибудь скромное (скажем, 1000 микросекунд).

person fadden    schedule 18.02.2014
comment
Спасибо, это сработало. Когда бы вы тогда установили тайм-аут -1? - person burakk; 19.02.2014
comment
Я бы не стал использовать его как постоянное значение. Если вы ждете самый первый входной буфер или вы отправили входной EOS и просто ждете, пока выходной поток будет истощаться, нет смысла зацикливаться. Таким образом, вы можете установить переменную в -1, сначала дождаться ввода, затем установить переменную в 1000 и использовать ее до тех пор, пока у вас не закончится ввод, после чего вы вернете ее в -1. (Я хотел сделать что-то подобное в Grafika с тех пор, как возник вопрос о задержке запуска, но я еще не дошел до этого.) - person fadden; 19.02.2014