使用Android MediaCodec从相机编码H.264

我试图让这个工作在Android 4.1(使用升级的华硕Transformer平板电脑)。 感谢Alex对我以前的问题的回应 ,我已经能够将一些原始的H.264数据写入一个文件,但是这个文件只能用ffplay -f h264ffplay -f h264 ,好像丢失了关于帧率的所有信息快速播放)。 此外颜色空间看起来不正确(atm使用摄像机在编码器侧的默认值)。

 public class AvcEncoder { private MediaCodec mediaCodec; private BufferedOutputStream outputStream; public AvcEncoder() { File f = new File(Environment.getExternalStorageDirectory(), "Download/video_encoded.264"); touch (f); try { outputStream = new BufferedOutputStream(new FileOutputStream(f)); Log.i("AvcEncoder", "outputStream initialized"); } catch (Exception e){ e.printStackTrace(); } mediaCodec = MediaCodec.createEncoderByType("video/avc"); MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 320, 240); mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000); mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15); mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar); mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mediaCodec.start(); } public void close() { try { mediaCodec.stop(); mediaCodec.release(); outputStream.flush(); outputStream.close(); } catch (Exception e){ e.printStackTrace(); } } // called from Camera.setPreviewCallbackWithBuffer(...) in other class public void offerEncoder(byte[] input) { try { ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers(); int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); inputBuffer.put(input); mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, 0, 0); } MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,0); while (outputBufferIndex >= 0) { ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; byte[] outData = new byte[bufferInfo.size]; outputBuffer.get(outData); outputStream.write(outData, 0, outData.length); Log.i("AvcEncoder", outData.length + " bytes written"); mediaCodec.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0); } } catch (Throwable t) { t.printStackTrace(); } } 

将编码器types更改为“video / mp4”显然可以解决帧率问题,但是由于主要目标是制作stream媒体服务,因此这不是一个好的解决scheme。

我知道我考虑到SPS和PPS NALU,删除了Alex的一些代码,但是我希望这是不必要的,因为这些信息也是来自outData ,我假定编码器会正确地格式化。 如果情况并非如此,我应该如何在我的文件/stream中安排不同types的NALU?

那么,为了制作一个有效的H.264stream,我在这里错过了什么? 我应该使用哪些设置来使相机的色彩空间和编码器的色彩空间相匹配?

我有一种感觉,这是一个比Android / MediaCodec主题更多的H.264相关的问题。 或者我仍然没有正确使用MediaCodec API?

提前致谢。

对于您的快速回放 – 帧速率问题,这里没有什么你必须做的。 由于这是一个stream媒体解决scheme,所以必须提前告知对方帧速率,或者每个帧需要时间戳。 这两个都不是基本stream的一部分。 要么select预先确定的帧速率,要么传递一些sdp或类似的东西,或者使用现有的协议如rtsp。 在第二种情况下,时间戳是以类似rtp的forms发送的stream的一部分。 然后客户端必须deprt rtpstream并播放它。 这是基本stream媒体的工作原理。 [要么固定你的帧速率,如果你有一个固定的速率编码器或给时间戳]

本地电脑播放会很快,因为它不会知道fps。 通过在input前给出fps参数,例如

 ffplay -fps 30 in.264 

您可以控制PC上的播放。

至于文件不可播放:是否有SPS和PPS。 你也应该有NAL头文件 – 附件B格式。 我对android并不了解,但这是h.264基本stream的要求,当它们不在任何容器中时需要被播放和播放。 如果android默认是mp4的,但默认annexb标题将被closures,所以也许有一个开关来启用它。 或者,如果您逐帧获取数据,请自行添加。

至于颜色格式:我猜想默认应该工作。 所以不要设置它。 如果不尝试422平面或UVYV / VYUY交错格式。 通常相机就是其中之一。 (但不是必要的,这些可能是我经常遇到的那些)。

Android 4.3(API 18)提供了一个简单的解决scheme。 MediaCodec类现在接受来自Surfaces的input,这意味着您可以将相机的Surface预览连接到编码器,并绕过所有奇怪的YUV格式问题。

还有一个新的MediaMuxer类 ,将您的原始H.264stream转换为.mp4文件(可选地在audiostream中混合)。

请参阅CameraToMpegTest源代码,了解如何执行此操作。 (它还演示了使用OpenGL ES片段着色器在录制video时对video执行一个简单的编辑。)

如果将预览色彩空间设置为YV12,则可以像这样转换色彩空间:

 public static byte[] YV12toYUV420PackedSemiPlanar(final byte[] input, final byte[] output, final int width, final int height) { /* * COLOR_TI_FormatYUV420PackedSemiPlanar is NV12 * We convert by putting the corresponding U and V bytes together (interleaved). */ final int frameSize = width * height; final int qFrameSize = frameSize/4; System.arraycopy(input, 0, output, 0, frameSize); // Y for (int i = 0; i < qFrameSize; i++) { output[frameSize + i*2] = input[frameSize + i + qFrameSize]; // Cb (U) output[frameSize + i*2 + 1] = input[frameSize + i]; // Cr (V) } return output; } 

要么

  public static byte[] YV12toYUV420Planar(byte[] input, byte[] output, int width, int height) { /* * COLOR_FormatYUV420Planar is I420 which is like YV12, but with U and V reversed. * So we just have to reverse U and V. */ final int frameSize = width * height; final int qFrameSize = frameSize/4; System.arraycopy(input, 0, output, 0, frameSize); // Y System.arraycopy(input, frameSize, output, frameSize + qFrameSize, qFrameSize); // Cr (V) System.arraycopy(input, frameSize + qFrameSize, output, frameSize, qFrameSize); // Cb (U) return output; } 

您可以查询MediaCodec支持的位图格式并查询您的预览。 问题是,一些MediaCodecs只支持专有的打包YUV格式,你不能从预览中获得。 特别是2130706688 = 0x7F000100 = COLOR_TI_FormatYUV420PackedSemiPlanar。 默认的预览格式是17 = NV21 = MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV411Planar = YCbCr 420 Semi Planar

如果您没有明确要求另一个像素格式,则相机预览缓冲区将以YUV 420格式(称为NV21)到达 ,对此COLOR_FormatYCrYCb是MediaCodec等价物。

不幸的是,正如本页其他答案所述,不能保证在您的设备上,AVC编码器支持这种格式。 请注意,有一些奇怪的设备不支持NV21,但我不知道任何可以升级到API 16(因此,有MediaCodec)。

Google文档还声称YV12平面YUV必须作为相机预览格式支持所有API> = 12的设备。因此,尝试它可能会很有用(MediaCodec相当于您在代码段中使用的COLOR_FormatYUV420Planar )。

更新 :正如Andrew Cottrell所提醒的那样,YV12仍然需要色度交换才能成为COLOR_FormatYUV420Planar。