我正在使用 MediaCodec 实现原始 h264 播放器
I am implementing raw h264 player using MediaCodec
我正在实现原始的 h264 播放器。
以下代码运行缓慢。 (有互斥量问题,但忽略它。)
另外 bpkt.bytes 通过 tcp 从其他服务器发送名为结束帧的每个帧单元。
但是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();
}
});
}
}
}
这里的问题是只有在输入缓冲区可用时才耗尽输出缓冲区。
事实是 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);
...
}
}
我正在实现原始的 h264 播放器。
以下代码运行缓慢。 (有互斥量问题,但忽略它。)
另外 bpkt.bytes 通过 tcp 从其他服务器发送名为结束帧的每个帧单元。
但是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();
}
});
}
}
}
这里的问题是只有在输入缓冲区可用时才耗尽输出缓冲区。 事实是 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);
...
}
}