首页 > 解决方案 > 我正在使用 MediaCodec 实现原始 h264 播放器

问题描述

我正在实施原始 h264 播放器。

以下代码运行缓慢。(存在互斥问题,但忽略它。)

此外,bpkt.bytes 还会通过 tcp 从其他服务器发送每个名为 end frame 的帧单元。

但是 mH264Queue 消耗数据比填充数据到 mH264Queue 慢。

mH264Queue 的大小永远增加。

请看我的代码。

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;

import android.app.Activity;
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaFormat;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.Toast;

public class RawH264Activity extends Activity implements SurfaceHolder.Callback
{

//    private static final String filePath = Environment.getExternalStorageDirectory()+ "/ksoo.h264"; // + "/video_encoded.263";//"/video_encoded.264";
    private PlayerThread mPlayer = null;
    Handler handler = null;
//    public static byte[] SPS = null;
//    public static byte[] PPS = null;
    Socket mMediaSocket = null;
    BufferedInputStream mMediaSocketIn = null;
    OutputStream mMediaSocketOut = null;
    Thread mSocketTh = null;
    Queue<SocketTool.BinaryPkt> mH264Queue = new LinkedList<SocketTool.BinaryPkt>();
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        SurfaceView sv = new SurfaceView(this);
        handler = new Handler();
        sv.getHolder().addCallback(this);
        setContentView(sv);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder)
    {
        Log.d("DecodeActivity", "in surfaceCreated");
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
    {
        Log.d("DecodeActivity", "in surfaceChanged");
        if (mPlayer == null)
        {
            Toast.makeText(getApplicationContext(), "in surfaceChanged. creating playerthread", Toast.LENGTH_SHORT).show();
            mSocketTh = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        mMediaSocket = new Socket(CurrentState.get().mRemoteAddress, CurrentState.get().mRemoteMediaPort);
                        mMediaSocketIn = new BufferedInputStream(mMediaSocket.getInputStream());
                        mMediaSocketOut = mMediaSocket.getOutputStream();


                        mPlayer = new PlayerThread(holder.getSurface());
                        mPlayer.start();
                        while(!mSocketTh.isInterrupted()) {
                            SocketTool.BinaryPkt bpkt = SocketTool.readPKTBinary(mMediaSocketIn);
                            mH264Queue.add(bpkt);
                            if(mH264Queue.size() > 2000)
                                break;
                        }
                        try {
                            mMediaSocketIn.close();
                            mMediaSocketOut.close();
                            mMediaSocket.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
//                        Log.i("bpkt", "" + bpkt.mCmdType);
//                    }
                    } catch (IOException e) {
                        // 서버 접속 끊기
                        e.printStackTrace();
                    }

                }
            });
            mSocketTh.start();
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder)
    {
        if (mPlayer != null)
        {
            mPlayer.interrupt();
        }
    }

    private class PlayerThread extends Thread
    {
        //private MediaExtractor extractor;
        private MediaCodec decoder;
        private Surface surface;

        public PlayerThread(Surface surface)
        {
            this.surface = surface;
        }

        @Override
        public void run()
        {
            handler.post(new Runnable()
            {
                @Override
                public void run()
                {

                    try {
                        decoder = MediaCodec.createDecoderByType("video/avc");
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 1280, 720);
//                    Video_format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, interval);
//                    mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
//                    mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
//                    mediaFormat.setByteBuffer("csd-0", ByteBuffer.wrap(new byte[] {
//                            0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1F, (byte)0x9D, (byte)0xA8, 0x14, 0x01, 0x6E, (byte)0x9B, (byte)0x80,
//                            (byte)0x80, (byte)0x80, (byte)0x81}));
//                    mediaFormat.setByteBuffer("csd-1", ByteBuffer.wrap(new byte[] {
//                            0x00, 0x00, 0x00, 0x01, 0x68, (byte)0xCE, 0x3C, (byte)0x80 }));
//                    byte[] header_sps  = { 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, (byte)0x80, 0x0C, (byte)0xE4, 0x40, (byte)0xA0, (byte)0xFD, 0x00, (byte)0xDA, 0x14, 0x26, (byte)0xA0 };
//                    byte[] header_pps = {0x00, 0x00, 0x00, 0x01, 0x68, (byte)0xCE, 0x38, (byte)0x80 };
//                    mediaFormat.setByteBuffer("csd-0", ByteBuffer.wrap(header_sps));
//                    mediaFormat.setByteBuffer("csd-1", ByteBuffer.wrap(header_pps));

                    decoder.configure(mediaFormat, surface /* surface */, null /* crypto */, 0 /* flags */);

                    if (decoder == null)
                    {
                        Log.e("DecodeActivity", "Can't find video info!");
                        return;
                    }

                    decoder.start();
                    Log.d("DecodeActivity", "decoder.start() called");

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


                    long startMs = System.currentTimeMillis();
                    int i = 0;
                    while(!Thread.interrupted())
                    {
                        int inIndex = 0;
                        while ((inIndex = decoder.dequeueInputBuffer(1)) < 0)
                            ;
                        if (inIndex >= 0)
                        {
                            ByteBuffer buffer = inputBuffers[inIndex];
                            buffer.clear();
                            Log.i("queue", String.valueOf(mH264Queue.size()));
                            SocketTool.BinaryPkt bpkt = mH264Queue.poll();
                            int sampleSize = 0;
                            if (bpkt.bytes == null) {
                                Log.d("DecodeActivity", "InputBuffer BUFFER_FLAG_END_OF_STREAM");
                                decoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                                break;
                            }
                            else
                            {
                                sampleSize = bpkt.bytes.length;
                                buffer.clear();
                                buffer.put(bpkt.bytes);
                                decoder.queueInputBuffer(inIndex, 0, sampleSize, 0, 0);
                            }


                            BufferInfo info = new BufferInfo();
                            int outIndex = decoder.dequeueOutputBuffer(info, 10000);
                            switch (outIndex)
                            {
                                case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                                    Log.d("DecodeActivity", "INFO_OUTPUT_BUFFERS_CHANGED");
                                    outputBuffers = decoder.getOutputBuffers();
                                    break;
                                case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
//                                    Log.d("DecodeActivity", "New format " + decoder.getOutputFormat());

                                    break;
                                case MediaCodec.INFO_TRY_AGAIN_LATER:
//                                    Log.d("DecodeActivity", "dequeueOutputBuffer timed out!");
                                    try {
                                        sleep(1);
                                    } catch (InterruptedException e) {
                                        // TODO Auto-generated catch block
                                        e.printStackTrace();
                                    }
                                    break;
                                default:
                                    ByteBuffer outbuffer = outputBuffers[outIndex];

//                                    Log.d("DecodeActivity", "We can't use this buffer but render it due to the API limit, " + outbuffer);
                                    decoder.releaseOutputBuffer(outIndex, true);
                                    break;
                            }
                            i++;
                        }
                    }

                    decoder.stop();
                    decoder.release();
                }
            });
        }
    }
}

标签: h.264android-mediacodec

解决方案


这里的问题是只有在输入缓冲区可用时才会耗尽输出缓冲区。事实上,媒体编解码器的输出通常(如果不是总是)在输入缓冲区排队后立即不可用。所以你应该做这样的事情

while(!endReached) {
    // Try drain decoder output first
    BufferInfo info = new BufferInfo();
    int outIndex = decoder.dequeueOutputBuffer(info, 10000);
    switch (outIndex) {
        ...
    }
    // Feed decoder input
    if (inputAvailable) {
        int inIndex = decoder.dequeueInputBuffer(10000);
        ...
    }
}

推荐阅读