热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

NDK中使用mediacodec解码h264

《Ndk中使用Mediacode解码》《androidmediacodec编码demo(java)》《NDK中使用mediacodec编码h264》《Androidnative层使

《Ndk中使用Mediacode解码》
《android mediacodec 编码demo(java)》
《NDK中使用mediacodec编码h264》
《Android native 层使用opengl渲染YUV420p和NV12》
《android 使用NativeWindow渲染RGB视频》
《opengl 叠加显示文字》
《android studio 编译freeType》
《最原始的yuv图像叠加文字的实现--手动操作像素》
常见的都是在java层使用mediacode解码,给mediacode绑定surface直接解码渲染,为了配合ffmpeg现在想在native用ndk使用mediacode硬解码,解码直接输出yuv数据,只是单纯地需要一个解码器,不绑定surface. 百度百度出来的文章怎么就没有浅显易懂直接可用的呢?非要上来就讲那张mediacode官方图。。。我想,既然是demo, 就应该是小白直接可用,没有过多的架构设计最好是一个main函数到底,多好。自己折腾老半天终于弄出来个可以用的demo,一个在android studio 上直接build 出可执行程序,生成的文件在 app/build/intermediates/ndkBuild/debug/obj/local/arm64-v8a/codec_demo  , 该程序从 一个h264裸流文件中循环读取h264帧,调用Mediacodec 解码得到NV21的yuv输出,然后写到输出文件中。记录下:

android studio上使用Android.mk : 需要在app::build.gradle 中的 android->defaultconfig中添加
ndk{
            abiFilters "arm64-v8a"
        }
android->下添加:
externalNativeBuild{
        ndkBuild{
            path file("src/main/jni/Android.mk") //里面是我们的Android.mk文件路径
        }
    }
用ndk可以把以下三个文件拷贝到一个名称为jni的目录下面,然后ndk-build(为什么要放到jni目录下,这个就得问ndk-build工具了),可以得到 codec_demo 可执行程序,push到设备上可以直接运行(前提,要放置好 h264源文件,没有纯粹的h264裸文件,可以使用ffmpeg 命令从MP4文件中抽取),部分源代码借鉴于 谷歌官方demo https://github.com/android/ndk-samples/blob/main/native-codec/app/src/main/cpp/native-codec-jni.cpp
在真机上运行 该demo 程序:

放上原代码:

//canok 20210123
//NdkMediacodec.cpp
#include
#include
#include
#include
#include "media/NdkMediaCodec.h"
#include "media/NdkMediaFormat.h"
#include "geth264Frame.cpp"#define LOGD printf
bool bRun = true;
AMediaCodec* pMediaCodec;
AMediaFormat *format ;
FILE *fp_out =NULL;int64_t getTimeNsec() {struct timespec now;clock_gettime(CLOCK_MONOTONIC, &now);return (int64_t) now.tv_sec*1000*1000*1000 + now.tv_nsec;
}
int64_t getTimeSec() {struct timespec now;clock_gettime(CLOCK_MONOTONIC, &now);return (int64_t)now.tv_sec;
}
int64_t getTimeMsec(){ //毫秒struct timespec now;clock_gettime(CLOCK_MONOTONIC, &now);return now.tv_sec*1000 +(int64_t)now.tv_nsec/(1000*1000);
}
int64_t getTimeUsec(){ //usstruct timespec now;clock_gettime(CLOCK_MONOTONIC, &now);return now.tv_sec*1000*1000 +(int64_t)now.tv_nsec/(1000);
}
int firstFrames =0;
void *run(void*pram){if(fp_out &#61;&#61;NULL){fp_out &#61; fopen("/storage/emulated/0/canok/yuv.data","w&#43;");if(fp_out&#61;&#61;NULL){LOGD("fopen erro!\n");return NULL;}}init("/storage/emulated/0/canok/test.h264");//https://github.com/android/ndk-samples/blob/main/native-codec/app/src/main/cpp/native-codec-jni.cpp//decode//这里设定名称// pMediaCodec &#61; AMediaCodec_createCodecByName("video/avc");//h264pMediaCodec &#61; AMediaCodec_createDecoderByType("video/avc");//h264format &#61; AMediaFormat_new();AMediaFormat_setString(format, "mime", "video/avc");AMediaFormat_setInt32(format,AMEDIAFORMAT_KEY_WIDTH,672);AMediaFormat_setInt32(format,AMEDIAFORMAT_KEY_HEIGHT,378);LOGD("[%d%s%s]\n",__LINE__,__FUNCTION__,__DATE__);//这里配置media_status_t status &#61; AMediaCodec_configure(pMediaCodec,format,NULL,/*可以在这制定native surface, 直接渲染*/NULL,0);//解码&#xff0c;flags 给0&#xff0c;编码给AMEDIACODEC_CONFIGURE_FLAG_ENCODEif(status!&#61;0){LOGD("erro config %d\n",status);}//启动AMediaCodec_start(pMediaCodec);int outFramecount &#61; 0;int inFramecount &#61;0;while(bRun){//无法做到理想的入一帧&#xff0c;就解码输出该帧。 解码需要参考。现在是多次输入&#xff0c;每一次输入成功都把当前所有已经解码完的取出。//1.0 取空buffer&#xff0c;填充数据&#xff0c;入队ssize_t bufidx &#61; AMediaCodec_dequeueInputBuffer(pMediaCodec,2000);//如果配置错误&#xff0c;比如format中的格式和宽高没有配置&#xff0c;这里get 会出错&#xff08;格式都不知道&#xff0c;怎么知道分配多大空间&#xff1f;&#xff09;&#xff0c;返回错误码&#xff0c;错误码的定义在&#xff1f;&#xff1f;&#xff1f;&#xff1f;//LOGD("input bufidx %d \n",bufidx);if(bufidx>&#61;0){ //当取不到空buffer的时候&#xff0c;有可能是解码慢跟不上输入速度&#xff0c;导致buffer不够用&#xff0c;所以还需要在后面继续取解码后的数据。size_t bufsize;uint8_t *buf&#61; AMediaCodec_getInputBuffer(pMediaCodec,bufidx,&bufsize);//get h264 frame: 并填充到 buf,//LOGD("bufsize %d\n",bufsize);int h264FrameLen &#61; getOneNal(buf,bufsize);if(h264FrameLen<&#61;0){//需要销毁。。。。。。LOGD("get over!!!!!\n");break;}// presentationTimeUs 就是 PTS 如果不要求渲染&#xff0c;这里可以随便。 也可以更具这一个值&#xff0c;来确定每一帧的身份&#xff0c;解码完后的数据里也有这个值。//注意当前例子中&#xff0c;上面 getOneNal 并不是获取到一帧完整数据&#xff0c;有可能是 sps pps, 这种情况就不会有对应的 一帧输出。uint64_t presentationTimeUs &#61; getTimeUsec();LOGD("in framecount %d :%lld\n",inFramecount&#43;&#43;,presentationTimeUs);//入队列 给到解码器AMediaCodec_queueInputBuffer(pMediaCodec,bufidx,0,h264FrameLen,presentationTimeUs,0);}//2.0 取输出&#xff0c;拿走数据&#xff0c;归还buffersize_t bufsize;uint8_t *buf&#61;NULL;AMediaCodecBufferInfo info;do{bufidx &#61; AMediaCodec_dequeueOutputBuffer(pMediaCodec, &info, 2000);//取数据&#xff0c;一直到取到解码后的数据//LOGD("out bufidx %d \n",bufidx);if (bufidx >&#61; 0) {int framelen &#61; 0;{int mWidth, mHeight;auto format &#61; AMediaCodec_getOutputFormat(pMediaCodec);AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &mWidth);AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &mHeight);int32_t localColorFMT;AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT,&localColorFMT);//framelen &#61; mWidth * mHeight * 1.5; //这里干脆自己算大小了&#xff0c;是不是也应该有一个 键值存储可以直接来获取&#xff1f;framelen &#61; info.size;LOGD("out: outFramecount %d %lld ", outFramecount,info.presentationTimeUs);LOGD("out:[%d]X[%d]%d,%d ", mWidth, mHeight, localColorFMT,framelen); //21 &#61;&#61; nv21格式 具体的定义在哪里&#xff1f;&#xff1f;&#xff1f;}//在这里取走解码后的数据&#xff0c;//然后释放buffer给解码器。buf &#61; AMediaCodec_getOutputBuffer(pMediaCodec, bufidx, &bufsize);LOGD("%d[%ld:%ld]out data:%d \n", outFramecount&#43;&#43;, getTimeSec(), getTimeMsec(),bufsize);//bufsize 并不是有效数据的大小。//fwrite(buf,1,bufsize,fp_out);fwrite(buf, 1, framelen, fp_out);AMediaCodec_releaseOutputBuffer(pMediaCodec, bufidx, false);} else if (bufidx &#61;&#61; AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {// 解码输出的格式发生变化int mWidth, mHeight;auto format &#61; AMediaCodec_getOutputFormat(pMediaCodec);AMediaFormat_getInt32(format, "width", &mWidth);AMediaFormat_getInt32(format, "height", &mHeight);int32_t localColorFMT;AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT,&localColorFMT);}else if(bufidx &#61;&#61; AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {}else {}}while(bufidx>0);// 一直取到没数据&#xff0c;把之前解码完的都取出来}
}
int main(int argc, const char*argv[]){//if(argc !&#61; 4){// LOGD("usage:filename,width,height\n");// return -1;// }int ret &#61;0;pthread_t pid;if((ret&#61;pthread_create(&pid,NULL,run,NULL)) !&#61;0 ){LOGD("thread_create err\n");return -1;}while(1){usleep(1000*1000);}}

/******20190828 canok*** output: complete frames**/
//geth264Frame.cpp 用来从h264文件中循环读取 帧数据
#include
#include
#include
#include #define MIN(a,b) ((a)<(b)?(a):(b))typedef unsigned char uint8_t; //无符号8位数#define NALU_TYPE_SLICE 1
#define NALU_TYPE_DPA 2
#define NALU_TYPE_DPB 3
#define NALU_TYPE_DPC 4
#define NALU_TYPE_IDR 5
#define NALU_TYPE_SEI 6
#define NALU_TYPE_SPS 7
#define NALU_TYPE_PPS 8
#define NALU_TYPE_AUD 9
#define NALU_TYPE_EOSEQ 10
#define NALU_TYPE_EOSTREAM 11
#define NALU_TYPE_FILL 12#define CACH_LEN (1024*1000)//缓冲区不能设置太小&#xff0c;如果出现比某一帧比缓冲区大&#xff0c;会被覆盖掉一部分
static uint8_t *g_cach[2] &#61; {NULL,NULL};
static FILE* fp_inH264 &#61; NULL;
static int icach &#61; 0;
static int ioffset &#61; 0;
static int bLoop &#61; 1;
static bool bInit&#61;false;
static int init()
{if(bInit){return 0;}else{bInit &#61; true;}if(g_cach[0] &#61;&#61; NULL){g_cach[0] &#61; (uint8_t*)malloc(CACH_LEN);}if(g_cach[1] &#61;&#61; NULL){g_cach[1] &#61; (uint8_t*)malloc(CACH_LEN);}if(fp_inH264 &#61;&#61; NULL){//fp_inH264 &#61; fopen("./live555.video","r");fp_inH264 &#61; fopen("/storage/emulated/0/canok/test.h264","r");if(fp_inH264 &#61;&#61; NULL){printf("fope erro [%d%s]\n",__LINE__,__FUNCTION__);return -1;}}if(fread(g_cach[icach], 1,CACH_LEN,fp_inH264 )}
static int deinit()
{if(g_cach[0]){free(g_cach[0]);g_cach[0] &#61; NULL;}if(g_cach[1]){free(g_cach[1]);g_cach[1] &#61; NULL;}if(fp_inH264){fclose(fp_inH264);fp_inH264 &#61; NULL;}return 0;
}
static int I_count &#61;0;
static int PB_count &#61; 0;
static int All_count &#61; 0;
static int SPS_count &#61;0;
static int PPS_count &#61;0;
static int AUD_count &#61;0;//分隔符
static int checkNal(uint8_t nalHeader)
{All_count &#43;&#43;;char type &#61; nalHeader & ((1<<5)-1);switch(type){case NALU_TYPE_SPS:PPS_count &#43;&#43;;printf("sps\n");break;case NALU_TYPE_PPS:SPS_count &#43;&#43;;printf("pps\n");break;case NALU_TYPE_IDR:I_count &#43;&#43;;printf("I slice !!!!!!!!!!!!!!\n");break;case NALU_TYPE_SLICE:PB_count &#43;&#43;;printf("B/P slice\n");break;case NALU_TYPE_AUD:// 结束符&#xff0c;没有实际数据AUD_count &#43;&#43;;printf("Delimiter&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;\n");break;default:printf("type :%d\n",type);}return type;
}
static int checkFlag(uint8_t *buffer, int offset)
{static uint8_t mMark[4] &#61; {0x00,0x00,0x00,0x01};return !memcmp(buffer&#43;offset,mMark,4);//return (!memcmp(buffer&#43;offset,mMark,4) && ((buffer[offset&#43;4]&((1<<5)-1)) &#61;&#61; 9) );}
//获取一个Nal到 buf, bufLen表示缓冲区最大可以容纳的数据
//返回实际的帧数据长度
static int getOneNal(uint8_t *buf, int bufLen)
{if(!bInit){init();}int i &#61;0;int startpoint &#61; ioffset;int endpoint &#61; ioffset;for (i &#61; ioffset&#43;4; i <&#61; CACH_LEN - 4; i&#43;&#43;) {if (checkFlag(g_cach[icach], i)){startpoint &#61; ioffset;endpoint &#61; i;break;}}if(endpoint - startpoint > 0){int dataLen &#61; endpoint -startpoint;if(bufLen int main()
{if(init()){return -1;}uint8_t *buffer &#61; (uint8_t*)malloc(CACH_LEN);int len &#61;0;FILE *fp_out &#61; fopen("out.h264","w&#43;");while((len &#61; getOneNal(buffer,CACH_LEN) )> 0){printf("get a Nal len:%8d-----",len);checkNal(buffer[4]);fwrite(buffer,1,len,fp_out);}fclose(fp_out);free(buffer);deinit();printf("All_count %d\n",All_count);printf("I_count %d\n",I_count);printf("PB_count %d\n",PB_count);printf("AUD_count %d\n",AUD_count);printf("SPS_count %d\n",SPS_count);printf("PPS_count %d\n",PPS_count);
}
#endif

//Android.mk
LOCAL_PATH :&#61; $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:&#61; codec_demo
LOCAL_SRC_FILES :&#61; NdkMediacodec.cpp
#LOCAL_SHARED_LIBRARIES :&#61; libandroid libmediandk liblog //android studio 上这样不行&#xff1f;&#xff1f;&#xff1f;提示没有定义的模块&#xff1f; 垃圾&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 听说要指定为 platform 21.................
LOCAL_LDLIBS :&#61; -lmediandk
LOCAL_LDFLAGS &#43;&#61; -pie -fPIE
include $(BUILD_EXECUTABLE)


推荐阅读
  • 本文概述了JNI的原理以及常用方法。JNI提供了一种Java字节码调用C/C++的解决方案,但引用类型不能直接在Native层使用,需要进行类型转化。多维数组(包括二维数组)都是引用类型,需要使用jobjectArray类型来存取其值。此外,由于Java支持函数重载,根据函数名无法找到对应的JNI函数,因此介绍了JNI函数签名信息的解决方案。 ... [详细]
  • 本文讨论了如何使用GStreamer来删除H264格式视频文件中的中间部分,而不需要进行重编码。作者提出了使用gst_element_seek(...)函数来实现这个目标的思路,并提到遇到了一个解决不了的BUG。文章还列举了8个解决方案,希望能够得到更好的思路。 ... [详细]
  • 基于STM32的智能循迹小车设计(基础版)
    基于STM32的智能循迹小车设计(基础版)硬件准备1、小车底盘+四直流电机(带轮)2、STM32F103C8T6核心板3、12V8700mAh锂电池(可以用几节18650锂电池)4 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • 如何搭建Java开发环境并开发WinCE项目
    本文介绍了如何搭建Java开发环境并开发WinCE项目,包括搭建开发环境的步骤和获取SDK的几种方式。同时还解答了一些关于WinCE开发的常见问题。通过阅读本文,您将了解如何使用Java进行嵌入式开发,并能够顺利开发WinCE应用程序。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • 树莓派语音控制的配置方法和步骤
    本文介绍了在树莓派上实现语音控制的配置方法和步骤。首先感谢博主Eoman的帮助,文章参考了他的内容。树莓派的配置需要通过sudo raspi-config进行,然后使用Eoman的控制方法,即安装wiringPi库并编写控制引脚的脚本。具体的安装步骤和脚本编写方法在文章中详细介绍。 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • 如何提高PHP编程技能及推荐高级教程
    本文介绍了如何提高PHP编程技能的方法,推荐了一些高级教程。学习任何一种编程语言都需要长期的坚持和不懈的努力,本文提醒读者要有足够的耐心和时间投入。通过实践操作学习,可以更好地理解和掌握PHP语言的特异性,特别是单引号和双引号的用法。同时,本文也指出了只走马观花看整体而不深入学习的学习方式无法真正掌握这门语言,建议读者要从整体来考虑局部,培养大局观。最后,本文提醒读者完成一个像模像样的网站需要付出更多的努力和实践。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 本文记录了作者对x265开源代码的实现与框架进行学习与探索的过程,包括x265的下载地址与参考资料,以及在Win7 32 bit PC、VS2010平台上的安装与配置步骤。 ... [详细]
  • Python脚本编写创建输出数据库并添加模型和场数据的方法
    本文介绍了使用Python脚本编写创建输出数据库并添加模型数据和场数据的方法。首先导入相应模块,然后创建输出数据库并添加材料属性、截面、部件实例、分析步和帧、节点和单元等对象。接着向输出数据库中添加场数据和历程数据,本例中只添加了节点位移。最后保存数据库文件并关闭文件。文章还提供了部分代码和Abaqus操作步骤。另外,作者还建立了关于Abaqus的学习交流群,欢迎加入并提问。 ... [详细]
  • MediaCodec视频解码流程详解及参考demo
    一、MediaCodec简介MediaCodec是Android自带的底层多媒体支持架构的一部分(通常与MediaExtractor,MediaSync ... [详细]
author-avatar
860800156_64d713
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有