• Android MediaCodec将h264实时视频流数据解码为yuv,并转换yuv的颜色格式为nv21


    初始化mediacodec

    1. //宽高根据摄像头分辨率设置
    2. private int Width = 1280;
    3. private int Height = 720;
    4. private MediaCodec mediaCodec;
    5. private ByteBuffer[] inputBuffers;
    6. private void initMediaCodec(Surface surface) {
    7. try {
    8. Log.d(TAG, "onGetNetVideoData: ");
    9. //创建解码器 H264的Type为 AAC
    10. mediaCodec = MediaCodec.createDecoderByType("video/avc");
    11. //创建配置
    12. MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", Width, Height);
    13. //设置解码预期的帧速率【以帧/秒为单位的视频格式的帧速率的键】
    14. mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 20);
    15. mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420SemiPlanar);//
    16. // byte[] headerSps = {0, 0, 0, 1, 103, 66, 0, 41, -115, -115, 64, 80, 30, -48, 15, 8, -124, 83, -128};
    17. // byte[] headerPps = {0, 0, 0, 1, 104, -54, 67, -56};
    18. //
    19. // mediaFormat.setByteBuffer("csd-0", ByteBuffer.wrap(headerSps));
    20. // mediaFormat.setByteBuffer("csd-1", ByteBuffer.wrap(headerPps));
    21. //配置绑定mediaFormat和surface
    22. mediaCodec.configure(mediaFormat, null, null, 0);
    23. mediaCodec.start();
    24. } catch (IOException e) {
    25. e.printStackTrace();
    26. //创建解码失败
    27. Log.e(TAG, "创建解码失败");
    28. }
    29. inputBuffers = mediaCodec.getInputBuffers();
    30. }

    处理数据,解码h264数据为yuv格式

    这里传入的是h264格式的实时视频流数据。

    1. private void onFrame(byte[] buf, int offset, int length) {
    2. MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    3. //查询10000毫秒后,如果dSP芯片的buffer全部被占用,返回-1;存在则大于0
    4. int inIndex = mediaCodec.dequeueInputBuffer(10000);
    5. if (inIndex >= 0) {
    6. //根据返回的index拿到可以用的buffer
    7. ByteBuffer byteBuffer = inputBuffers[inIndex];
    8. //清空缓存
    9. byteBuffer.clear();
    10. //开始为buffer填充数据
    11. byteBuffer.put(buf);
    12. //填充数据后通知mediacodec查询inIndex索引的这个buffer,
    13. mediaCodec.queueInputBuffer(inIndex, 0, length, mCount * 20, 0);
    14. mCount++;
    15. } else {
    16. Log.i(TAG, "inIndex < 0");
    17. //等待查询空的buffer
    18. return;
    19. }
    20. //mediaCodec 查询 "mediaCodec的输出方队列"得到索引
    21. int outIndex = mediaCodec.dequeueOutputBuffer(info, 10000);
    22. Log.e(TAG, "解码输出outIndex " + outIndex);
    23. if (outIndex >= 0) {
    24. //dsp的byteBuffer无法直接使用
    25. ByteBuffer byteBuffer = mediaCodec.getOutputBuffer(outIndex);
    26. //设置偏移量
    27. byteBuffer.position(info.offset);
    28. byteBuffer.limit(info.size + info.offset);
    29. byte[] ba = new byte[byteBuffer.remaining()];
    30. byteBuffer.get(ba);
    31. //需要预先分配与NV12相同大小的字节数组
    32. byte[] yuv = new byte[ba.length];
    33. //不确定是什么颜色格式,挨个试的
    34. //convertI420ToNV21(ba, yuv, Width, Height);
    35. //convertYV12toNV21(ba, yuv, Width, Height);
    36. convertNV12toNV21(ba, yuv, Width, Height);
    37. NV21Data(yuv);
    38. //检查所支持的颜色格式
    39. // MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType("video/avc");
    40. // for (int i = 0; i < capabilities.colorFormats.length; i++) {
    41. // int format = capabilities.colorFormats[i];
    42. //
    43. // //华为平板:COLOR_FormatYUV420SemiPlanar、COLOR_FormatYUV420Planar
    44. // //魅族手机:COLOR_FormatYUV420SemiPlanar
    45. // //rk3588s: COLOR_FormatYUV420Planar、COLOR_FormatYUV420Flexible、COLOR_FormatYUV420PackedSemiPlanar、COLOR_FormatYUV420SemiPlanar
    46. // switch (format) {
    47. // case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar://(对应 I420 or YV12)
    48. // Log.i("COLOR_Format_TAG", "=========COLOR_FormatYUV420Planar");
    49. // byte[] convertNv21YUV420Planar = new byte[ba.length];
    50. // //不确定是什么颜色格式,挨个试的
    51. convertI420ToNV21(ba, convertNv21YUV420Planar, Width, Height);
    52. convertYV12toNV21(ba, convertNv21YUV420Planar, Width, Height);
    53. // long l1 = System.currentTimeMillis();
    54. // convertNV12toNV21(ba, convertNv21YUV420Planar, Width, Height);
    55. // Log.i("耗时测试", "转为nv21的耗时: " + (System.currentTimeMillis() - l1));
    56. // long l2 = System.currentTimeMillis();
    57. // NV21Data(convertNv21YUV420Planar);
    58. // Log.i("耗时测试", "识别耗时: " + (System.currentTimeMillis() - l2));
    59. // continue;
    60. //
    61. // case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar://NV12
    62. // Log.i("COLOR_Format_TAG", "=======COLOR_FormatYUV420SemiPlanar");
    63. // byte[] nv21YUV420SemiPlanar = new byte[ba.length];
    64. // convertNV12toNV21(ba, nv21YUV420SemiPlanar, Width, Height);
    65. // NV21Data(nv21YUV420SemiPlanar);
    66. //
    67. // continue;
    68. // case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
    69. // Log.i("COLOR_Format_TAG", "=======COLOR_FormatYUV420PackedSemiPlanar");
    70. // byte[] nv21YUV420PackedSemiPlanar = new byte[ba.length];
    71. // convertNV12toNV21(ba, nv21YUV420PackedSemiPlanar, Width, Height);
    72. // NV21Data(nv21YUV420PackedSemiPlanar);
    73. // continue;
    74. // case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible:
    75. // byte[] nv21YUV420YUV420Flexible = new byte[ba.length];
    76. // convertNV12toNV21(ba, nv21YUV420YUV420Flexible, Width, Height);
    77. // NV21Data(nv21YUV420YUV420Flexible);
    78. // Log.i("COLOR_Format_TAG", "=======COLOR_FormatYUV420Flexible");
    79. // continue;
    80. // default:
    81. // continue;
    82. //
    83. // }
    84. //
    85. // }
    86. //如果surface绑定了,则直接输入到surface渲染并释放
    87. mediaCodec.releaseOutputBuffer(outIndex, false);
    88. } else {
    89. Log.e(TAG, "没有解码成功");
    90. }
    91. }

    处理获取到的nv21颜色格式的yuv数据

    1. private int printImageStatus = 0;
    2. private void NV21Data(byte[] nv21) {
    3. //将nv21视频流数据传入YuvImage中,转换成bitmap之后,显示在imageview上、
    4. //或者保存为png图片到本地,如果不出现灰色、不出现蓝色图像和红色图像颜色颠倒,
    5. //图像显示正常,则说明是标准的nv21格式视频流数据
    6. YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21, Width, Height, null);
    7. ByteArrayOutputStream baos = new ByteArrayOutputStream();
    8. yuvImage.compressToJpeg(new Rect(0, 0, Width, Height), 100, baos);
    9. byte[] data = baos.toByteArray();
    10. Log.i(TAG, "NV21Data-data: " + data.length);
    11. Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
    12. if (bitmap != null) {
    13. runOnUiThread(new Runnable() {
    14. @Override
    15. public void run() {
    16. mIvShowImage.setImageBitmap(bitmap);
    17. }
    18. });
    19. //保存bitmap为png图片
    20. if (printImageStatus == 0) {
    21. printImageStatus = 1;
    22. try {
    23. File myCaptureFile = new File(Environment.getExternalStorageDirectory(), "img.png");
    24. BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(myCaptureFile));
    25. bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
    26. bos.flush();
    27. bos.close();
    28. } catch (Exception e) {
    29. e.printStackTrace();
    30. }
    31. }
    32. }
    33. }

     yuv视频数据颜色格式转换

    1. public static void convertI420ToNV21(byte[] i420, byte[] nv21, int width, int height) {
    2. System.arraycopy(i420, 0, nv21, 0, width * height);
    3. int offset = width * height;
    4. for (int i = 0; i < width * height / 4; i++) {
    5. nv21[offset + 2 * i] = i420[offset + i + width * height / 4];
    6. nv21[offset + 2 * i + 1] = i420[offset + i];
    7. }
    8. }
    9. public static void convertYV12toNV21(byte[] yv12, byte[] nv21, int width, int height) {
    10. int size = width * height;
    11. int vOffset = size;
    12. int uOffset = size + (size / 4);
    13. // Copy Y channel as it is
    14. System.arraycopy(yv12, 0, nv21, 0, size);
    15. for (int i = 0; i < size / 4; i++) {
    16. nv21[vOffset + (i * 2)] = yv12[vOffset + i]; // V
    17. nv21[vOffset + (i * 2) + 1] = yv12[uOffset + i]; // U
    18. }
    19. }
    20. public static void convertNV12toNV21(byte[] nv12, byte[] nv21, int width, int height) {
    21. int size = width * height;
    22. int offset = size;
    23. // copy Y channel as it is
    24. System.arraycopy(nv12, 0, nv21, 0, offset);
    25. for (int i = 0; i < size / 4; i++) {
    26. nv21[offset + (i * 2) + 1] = nv12[offset + (i * 2)]; // U
    27. nv21[offset + (i * 2)] = nv12[offset + (i * 2) + 1]; // V
    28. }
    29. }

    h264实时视频流的数据来源

    1. @Override
    2. public void onPacketEvent(byte[] data) {
    3. onFrame(data, 0, data.length);
    4. //写入h264视频流到sdcard中
    5. //wirte2file(data, data.length);
    6. }

    写入h264视频流到sdcard中

    1. private BufferedOutputStream BufOs = null;
    2. private File destfile = null;
    3. private FileOutputStream destfs = null;
    4. private String dsetfilePath = Environment.getExternalStorageDirectory() + "/" + "test.h264";
    5. private void wirte2file(byte[] buf, int length) {
    6. if (isStart) {
    7. if (BufOs == null) {
    8. destfile = new File(dsetfilePath);
    9. try {
    10. destfs = new FileOutputStream(destfile);
    11. BufOs = new BufferedOutputStream(destfs);
    12. Log.d(TAG, "wirte2file-new ");
    13. } catch (FileNotFoundException e) {
    14. // TODO: handle exception
    15. Log.i("TRACK", "initerro" + e.getMessage());
    16. Log.d(TAG, "wirte2file-FileNotFoundException:" + e.getMessage());
    17. e.printStackTrace();
    18. }
    19. }
    20. try {
    21. BufOs.write(buf, 0, length);
    22. BufOs.flush();
    23. Log.d(TAG, "wirte2file-write");
    24. } catch (Exception e) {
    25. Log.d(TAG, "wirte2file-e: " + e.getMessage());
    26. // TODO: handle exception
    27. }
    28. }
    29. }
    30. private boolean isStart;
    31. public void onStop(View view) {
    32. isStart = false;
    33. Toast.makeText(this, "停止保存", Toast.LENGTH_SHORT).show();
    34. }
    35. public void onStart(View view) {
    36. isStart = true;
    37. Toast.makeText(this, "开始保存", Toast.LENGTH_SHORT).show();
    38. }

    rtsp获取h264实时视频流数据

    1. public class FFDemuxJava {
    2. static {
    3. System.loadLibrary("demux");
    4. }
    5. private long m_handle = 0;
    6. private EventCallback mEventCallback = null;
    7. public void init(String url) {
    8. m_handle = native_Init(url);
    9. }
    10. public void Start() {
    11. native_Start(m_handle);
    12. }
    13. public void stop() {
    14. native_Stop(m_handle);
    15. }
    16. public void unInit() {
    17. native_UnInit(m_handle);
    18. }
    19. public void addEventCallback(EventCallback callback) {
    20. mEventCallback = callback;
    21. }
    22. private void playerEventCallback(int msgType, float msgValue) {
    23. if(mEventCallback != null)
    24. mEventCallback.onMessageEvent(msgType, msgValue);
    25. }
    26. private void packetEventCallback(byte[]data) {
    27. if(mEventCallback != null)
    28. mEventCallback.onPacketEvent(data);
    29. }
    30. private native long native_Init(String url);
    31. private native void native_Start(long playerHandle);
    32. private native void native_Stop(long playerHandle);
    33. private native void native_UnInit(long playerHandle);
    34. public interface EventCallback {
    35. void onMessageEvent(int msgType, float msgValue);
    36. void onPacketEvent(byte []data);
    37. }
    38. }
     编写C代码加载ffmpeg库
    1. #include
    2. #include
    3. #include "FFBridge.h"
    4. extern "C"
    5. {
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. };
    15. extern "C" JNIEXPORT jstring JNICALL
    16. Java_com_qmcy_demux_MainActivity_stringFromJNI(
    17. JNIEnv* env,
    18. jobject /* this */) {
    19. std::string hello = "Hello from C++";
    20. return env->NewStringUTF(hello.c_str());
    21. }
    22. extern "C" JNIEXPORT jstring JNICALL
    23. Java_com_qmcy_demux_MainActivity_GetVersion(
    24. JNIEnv* env,
    25. jobject /* this */) {
    26. char strBuffer[1024 * 4] = {0};
    27. strcat(strBuffer, "libavcodec : ");
    28. strcat(strBuffer, AV_STRINGIFY(LIBAVCODEC_VERSION));
    29. strcat(strBuffer, "\nlibavformat : ");
    30. strcat(strBuffer, AV_STRINGIFY(LIBAVFORMAT_VERSION));
    31. strcat(strBuffer, "\nlibavutil : ");
    32. strcat(strBuffer, AV_STRINGIFY(LIBAVUTIL_VERSION));
    33. strcat(strBuffer, "\nlibavfilter : ");
    34. strcat(strBuffer, AV_STRINGIFY(LIBAVFILTER_VERSION));
    35. strcat(strBuffer, "\nlibswresample : ");
    36. strcat(strBuffer, AV_STRINGIFY(LIBSWRESAMPLE_VERSION));
    37. strcat(strBuffer, "\nlibswscale : ");
    38. strcat(strBuffer, AV_STRINGIFY(LIBSWSCALE_VERSION));
    39. strcat(strBuffer, "\navcodec_configure : \n");
    40. strcat(strBuffer, avcodec_configuration());
    41. strcat(strBuffer, "\navcodec_license : ");
    42. strcat(strBuffer, avcodec_license());
    43. //LOGCATE("GetFFmpegVersion\n%s", strBuffer);
    44. return env->NewStringUTF(strBuffer);
    45. }
    46. extern "C" JNIEXPORT jlong JNICALL Java_com_qmcy_demux_FFDemuxJava_native_1Init
    47. (JNIEnv *env, jobject obj, jstring jurl)
    48. {
    49. const char* url = env->GetStringUTFChars(jurl, nullptr);
    50. FFBridge *bridge = new FFBridge();
    51. bridge->Init(env, obj, const_cast<char *>(url));
    52. env->ReleaseStringUTFChars(jurl, url);
    53. return reinterpret_cast(bridge);
    54. }
    55. extern "C"
    56. JNIEXPORT void JNICALL Java_com_qmcy_demux_FFDemuxJava_native_1Start
    57. (JNIEnv *env, jobject obj, jlong handle)
    58. {
    59. if(handle != 0)
    60. {
    61. FFBridge *bridge = reinterpret_cast(handle);
    62. bridge->Start();
    63. }
    64. }
    65. extern "C"
    66. JNIEXPORT void JNICALL Java_com_qmcy_demux_FFDemuxJava_native_1Stop
    67. (JNIEnv *env, jobject obj, jlong handle)
    68. {
    69. if(handle != 0)
    70. {
    71. FFBridge *bridge = reinterpret_cast(handle);
    72. bridge->Stop();
    73. }
    74. }
    75. extern "C"
    76. JNIEXPORT void JNICALL Java_com_qmcy_demux_FFDemuxJava_native_1UnInit
    77. (JNIEnv *env, jobject obj, jlong handle)
    78. {
    79. if(handle != 0)
    80. {
    81. FFBridge *bridge = reinterpret_cast(handle);
    82. bridge->UnInit();
    83. delete bridge;
    84. }
    85. }

    源码地址icon-default.png?t=N7T8https://gitee.com/baipenggui/demux_demo.git

  • 相关阅读:
    PHP留言反馈管理系统源码
    【微服务】Nacos2.x服务发现?RPC调用?重试机制?
    力扣:17-电话号码的字母组合
    JS逆向实战9——cookies DES加密混淆
    三面拼多多都没过,惨败在(java中间件、数据库与spring框架)后悔没有早点知道这些
    Go方法特性详解:简单性和高效性的充分体现
    学习java第一百零七天
    2023中国传媒产业发展报告
    SSM - Springboot - MyBatis-Plus 全栈体系(十四)
    轻量级接口自动化测试框架
  • 原文地址:https://blog.csdn.net/qq_40116418/article/details/133351850