如何在 Android MediaCodec 中播放原始 NAL 单元
How to play raw NAL units in Android MediaCodec
我最初尝试 但我注意到我将不得不在低级别上做事。
我找到了这个 simple MediaCodec example。如您所见,它是一个在传递给它的表面上播放文件的线程。
注意线条
mExtractor = new MediaExtractor();
mExtractor.setDataSource(filePath);
看来我应该创建自己的 MediaExtractor
,它不会从文件中提取视频单元,而是使用我提供的缓冲区中的 h264 NAL 单元。
然后我可以调用 mExtractor.setDataSource(MediaDataSource dataSource)
,请参阅 MediaDataSource
它有 readAt(long position, byte[] buffer, int offset, int size)
这是它读取 NAL 单元的地方。但是,我应该如何通过它们?我没有关于需要读取的缓冲区结构的信息。
我应该传递一个包含 NAL 单元的 byte[] buffer
吗?如果是,采用哪种格式?什么是偏移量?如果它是一个缓冲区,我不应该只擦除已读取的行,因此没有偏移量或大小吗?
顺便说一句,h264 NAL 单元是流媒体单元,它们来自 RTP 数据包,而不是文件。我将通过 C++ 获取它们并将它们存储在缓冲区中并尝试传递给 mediaExtractor。
更新:
我已经阅读了很多有关 MediaCodec 的资料,我想我对它的理解更好了。根据 https://developer.android.com/reference/android/media/MediaCodec,一切都依赖于这种类型的东西:
MediaCodec codec = MediaCodec.createByCodecName(name);
MediaFormat mOutputFormat; // member variable
codec.setCallback(new MediaCodec.Callback() {
@Override
void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
// fill inputBuffer with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
@Override
void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
// bufferFormat is equivalent to mOutputFormat
// outputBuffer is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
}
@Override
void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
// Subsequent data will conform to new format.
// Can ignore if using getOutputFormat(outputBufferId)
mOutputFormat = format; // option B
}
@Override
void onError(…) {
…
}
});
codec.configure(format, …);
mOutputFormat = codec.getOutputFormat(); // option B
codec.start();
// wait for processing to complete
codec.stop();
codec.release();
如您所见,我可以传递输入缓冲区并获取解码后的输出缓冲区。确切的字节格式仍然是个谜,但我认为这就是它的工作原理。同样根据同一篇文章,ByteBuffer
s 的使用速度很慢,Surface
s 是首选。它们自动消耗输出缓冲区。虽然没有关于如何操作的教程,但文章中有一节说它几乎相同,所以我想我只需要添加额外的行
codec.setInputSurface(Surface inputSurface)
codec.setOutputSurface(Surface outputSurface)
其中 inputSurface
和 outputSurface
是 Surfaces which I pass to a MediaPlayer,我使用(如何)在 activity 中显示视频。并且输出缓冲区根本不会出现 onOutputBufferAvailable
(因为 surface
首先消耗它们),也不会 onInputBufferAvailable
.
所以现在的问题是:我究竟如何构建一个包含视频缓冲区的 Surface
,以及如何将 MediaPlayer
显示到 activity
对于输出,我可以简单地创建一个 Surface
并传递给 MediaPlayer
和 MediaCodec
,但是输入呢?无论如何我都需要 ByteBuffer
作为输入,而 Surface
只是为了使用其他输出作为输入吗?
您首先需要删除 NAL 单元,并将原始 H264 字节输入到此方法中,无论您是从文件中读取什么,所以不需要删除任何东西,因为您不使用数据包,只需输入此方法的数据字节:
rivate void initDecoder(){
try {
writeHeader = true;
if(mDecodeMediaCodec != null){
try{
mDecodeMediaCodec.stop();
}catch (Exception e){}
try{
mDecodeMediaCodec.release();
}catch (Exception e){}
}
mDecodeMediaCodec = MediaCodec.createDecoderByType(MIME_TYPE);
//MIME_TYPE = video/avc in your case
mDecodeMediaCodec.configure(format,mSurfaceView.getHolder().getSurface(),
null,
0);
mDecodeMediaCodec.start();
mDecodeInputBuffers = mDecodeMediaCodec.getInputBuffers();
} catch (IOException e) {
e.printStackTrace();
mLatch.trigger();
}
}
private void decode(byte[] data){
try {
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
int inputBufferIndex = mDecodeMediaCodec.dequeueInputBuffer(1000);//
if (inputBufferIndex >= 0) {
ByteBuffer buffer = mDecodeInputBuffers[inputBufferIndex];
buffer.clear();
buffer.put(data);
mDecodeMediaCodec.queueInputBuffer(inputBufferIndex,
0,
data.length,
packet.sequence / 1000,
0);
data = null;
//decodeDataBuffer.clear();
//decodeDataBuffer = null;
}
int outputBufferIndex = mDecodeMediaCodec.dequeueOutputBuffer(info,
1000);
do {
if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
//no output available yet
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
//encodeOutputBuffers = mDecodeMediaCodec.getOutputBuffers();
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
format = mDecodeMediaCodec.getOutputFormat();
//mediaformat changed
} else if (outputBufferIndex < 0) {
//unexpected result from encoder.dequeueOutputBuffer
} else {
mDecodeMediaCodec.releaseOutputBuffer(outputBufferIndex,
true);
outputBufferIndex = mDecodeMediaCodec.dequeueOutputBuffer(info,
0);
}
} while (outputBufferIndex > 0);
}
请不要忘记 iFrame(第一帧字节)包含敏感数据,必须首先提供给解码器
我最初尝试
我找到了这个 simple MediaCodec example。如您所见,它是一个在传递给它的表面上播放文件的线程。
注意线条
mExtractor = new MediaExtractor();
mExtractor.setDataSource(filePath);
看来我应该创建自己的 MediaExtractor
,它不会从文件中提取视频单元,而是使用我提供的缓冲区中的 h264 NAL 单元。
然后我可以调用 mExtractor.setDataSource(MediaDataSource dataSource)
,请参阅 MediaDataSource
它有 readAt(long position, byte[] buffer, int offset, int size)
这是它读取 NAL 单元的地方。但是,我应该如何通过它们?我没有关于需要读取的缓冲区结构的信息。
我应该传递一个包含 NAL 单元的 byte[] buffer
吗?如果是,采用哪种格式?什么是偏移量?如果它是一个缓冲区,我不应该只擦除已读取的行,因此没有偏移量或大小吗?
顺便说一句,h264 NAL 单元是流媒体单元,它们来自 RTP 数据包,而不是文件。我将通过 C++ 获取它们并将它们存储在缓冲区中并尝试传递给 mediaExtractor。
更新:
我已经阅读了很多有关 MediaCodec 的资料,我想我对它的理解更好了。根据 https://developer.android.com/reference/android/media/MediaCodec,一切都依赖于这种类型的东西:
MediaCodec codec = MediaCodec.createByCodecName(name);
MediaFormat mOutputFormat; // member variable
codec.setCallback(new MediaCodec.Callback() {
@Override
void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
// fill inputBuffer with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
@Override
void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
// bufferFormat is equivalent to mOutputFormat
// outputBuffer is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
}
@Override
void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
// Subsequent data will conform to new format.
// Can ignore if using getOutputFormat(outputBufferId)
mOutputFormat = format; // option B
}
@Override
void onError(…) {
…
}
});
codec.configure(format, …);
mOutputFormat = codec.getOutputFormat(); // option B
codec.start();
// wait for processing to complete
codec.stop();
codec.release();
如您所见,我可以传递输入缓冲区并获取解码后的输出缓冲区。确切的字节格式仍然是个谜,但我认为这就是它的工作原理。同样根据同一篇文章,ByteBuffer
s 的使用速度很慢,Surface
s 是首选。它们自动消耗输出缓冲区。虽然没有关于如何操作的教程,但文章中有一节说它几乎相同,所以我想我只需要添加额外的行
codec.setInputSurface(Surface inputSurface)
codec.setOutputSurface(Surface outputSurface)
其中 inputSurface
和 outputSurface
是 Surfaces which I pass to a MediaPlayer,我使用(如何)在 activity 中显示视频。并且输出缓冲区根本不会出现 onOutputBufferAvailable
(因为 surface
首先消耗它们),也不会 onInputBufferAvailable
.
所以现在的问题是:我究竟如何构建一个包含视频缓冲区的 Surface
,以及如何将 MediaPlayer
显示到 activity
对于输出,我可以简单地创建一个 Surface
并传递给 MediaPlayer
和 MediaCodec
,但是输入呢?无论如何我都需要 ByteBuffer
作为输入,而 Surface
只是为了使用其他输出作为输入吗?
您首先需要删除 NAL 单元,并将原始 H264 字节输入到此方法中,无论您是从文件中读取什么,所以不需要删除任何东西,因为您不使用数据包,只需输入此方法的数据字节:
rivate void initDecoder(){
try {
writeHeader = true;
if(mDecodeMediaCodec != null){
try{
mDecodeMediaCodec.stop();
}catch (Exception e){}
try{
mDecodeMediaCodec.release();
}catch (Exception e){}
}
mDecodeMediaCodec = MediaCodec.createDecoderByType(MIME_TYPE);
//MIME_TYPE = video/avc in your case
mDecodeMediaCodec.configure(format,mSurfaceView.getHolder().getSurface(),
null,
0);
mDecodeMediaCodec.start();
mDecodeInputBuffers = mDecodeMediaCodec.getInputBuffers();
} catch (IOException e) {
e.printStackTrace();
mLatch.trigger();
}
}
private void decode(byte[] data){
try {
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
int inputBufferIndex = mDecodeMediaCodec.dequeueInputBuffer(1000);//
if (inputBufferIndex >= 0) {
ByteBuffer buffer = mDecodeInputBuffers[inputBufferIndex];
buffer.clear();
buffer.put(data);
mDecodeMediaCodec.queueInputBuffer(inputBufferIndex,
0,
data.length,
packet.sequence / 1000,
0);
data = null;
//decodeDataBuffer.clear();
//decodeDataBuffer = null;
}
int outputBufferIndex = mDecodeMediaCodec.dequeueOutputBuffer(info,
1000);
do {
if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
//no output available yet
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
//encodeOutputBuffers = mDecodeMediaCodec.getOutputBuffers();
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
format = mDecodeMediaCodec.getOutputFormat();
//mediaformat changed
} else if (outputBufferIndex < 0) {
//unexpected result from encoder.dequeueOutputBuffer
} else {
mDecodeMediaCodec.releaseOutputBuffer(outputBufferIndex,
true);
outputBufferIndex = mDecodeMediaCodec.dequeueOutputBuffer(info,
0);
}
} while (outputBufferIndex > 0);
}
请不要忘记 iFrame(第一帧字节)包含敏感数据,必须首先提供给解码器