《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(){ //us
struct 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 ==NULL){
fp_out = fopen("/storage/emulated/0/canok/yuv.data","w+");
if(fp_out==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 = AMediaCodec_createCodecByName("video/avc");//h264
pMediaCodec = AMediaCodec_createDecoderByType("video/avc");//h264
format = 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 = AMediaCodec_configure(pMediaCodec,format,NULL,/*可以在这制定native surface, 直接渲染*/NULL,0);//解码,flags 给0,编码给AMEDIACODEC_CONFIGURE_FLAG_ENCODE
if(status!=0){
LOGD("erro config %d\n",status);
}
//启动
AMediaCodec_start(pMediaCodec);
int outFramecount = 0;
int inFramecount =0;
while(bRun){
//无法做到理想的入一帧,就解码输出该帧。 解码需要参考。现在是多次输入,每一次输入成功都把当前所有已经解码完的取出。
//1.0 取空buffer,填充数据,入队
ssize_t bufidx = AMediaCodec_dequeueInputBuffer(pMediaCodec,2000);
//如果配置错误,比如format中的格式和宽高没有配置,这里get 会出错(格式都不知道,怎么知道分配多大空间?),返回错误码,错误码的定义在????
//LOGD("input bufidx %d \n",bufidx);
if(bufidx>=0){ //当取不到空buffer的时候,有可能是解码慢跟不上输入速度,导致buffer不够用,所以还需要在后面继续取解码后的数据。
size_t bufsize;
uint8_t *buf= AMediaCodec_getInputBuffer(pMediaCodec,bufidx,&bufsize);
//get h264 frame: 并填充到 buf,
//LOGD("bufsize %d\n",bufsize);
int h264FrameLen = getOneNal(buf,bufsize);
if(h264FrameLen<=0){
//需要销毁。。。。。。
LOGD("get over!!!!!\n");
break;
}
// presentationTimeUs 就是 PTS 如果不要求渲染,这里可以随便。 也可以更具这一个值,来确定每一帧的身份,解码完后的数据里也有这个值。
//注意当前例子中,上面 getOneNal 并不是获取到一帧完整数据,有可能是 sps pps, 这种情况就不会有对应的 一帧输出。
uint64_t presentationTimeUs = getTimeUsec();
LOGD("in framecount %d :%lld\n",inFramecount++,presentationTimeUs);
//入队列 给到解码器
AMediaCodec_queueInputBuffer(pMediaCodec,bufidx,0,h264FrameLen,presentationTimeUs,0);
}
//2.0 取输出,拿走数据,归还buffer
size_t bufsize;
uint8_t *buf=NULL;
AMediaCodecBufferInfo info;
do{
bufidx = AMediaCodec_dequeueOutputBuffer(pMediaCodec, &info, 2000);
//取数据,一直到取到解码后的数据
//LOGD("out bufidx %d \n",bufidx);
if (bufidx >= 0) {
int framelen = 0;
{
int mWidth, mHeight;
auto format = 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 = mWidth * mHeight * 1.5; //这里干脆自己算大小了,是不是也应该有一个 键值存储可以直接来获取?
framelen = info.size;
LOGD("out: outFramecount %d %lld ", outFramecount,info.presentationTimeUs);
LOGD("out:[%d]X[%d]%d,%d ", mWidth, mHeight, localColorFMT,
framelen); //21 == nv21格式 具体的定义在哪里???
}
//在这里取走解码后的数据,
//然后释放buffer给解码器。
buf = AMediaCodec_getOutputBuffer(pMediaCodec, bufidx, &bufsize);
LOGD("%d[%ld:%ld]out data:%d \n", outFramecount++, getTimeSec(), getTimeMsec(),
bufsize);
//bufsize 并不是有效数据的大小。
//fwrite(buf,1,bufsize,fp_out);
fwrite(buf, 1, framelen, fp_out);
AMediaCodec_releaseOutputBuffer(pMediaCodec, bufidx, false);
} else if (bufidx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
// 解码输出的格式发生变化
int mWidth, mHeight;
auto format = 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 == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
}else {
}
}while(bufidx>0);// 一直取到没数据,把之前解码完的都取出来
}
}
int main(int argc, const char*argv[]){
//if(argc != 4){
// LOGD("usage:filename,width,height\n");
// return -1;
// }
int ret =0;
pthread_t pid;
if((ret=pthread_create(&pid,NULL,run,NULL)) !=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)//缓冲区不能设置太小,如果出现比某一帧比缓冲区大,会被覆盖掉一部分
static uint8_t *g_cach[2] = {NULL,NULL};
static FILE* fp_inH264 = NULL;
static int icach = 0;
static int ioffset = 0;
static int bLoop = 1;
static bool bInit=false;
static int init()
{
if(bInit){
return 0;
}else{
bInit = true;
}
if(g_cach[0] == NULL)
{
g_cach[0] = (uint8_t*)malloc(CACH_LEN);
}
if(g_cach[1] == NULL)
{
g_cach[1] = (uint8_t*)malloc(CACH_LEN);
}
if(fp_inH264 == NULL)
{
//fp_inH264 = fopen("./live555.video","r");
fp_inH264 = fopen("/storage/emulated/0/canok/test.h264","r");
if(fp_inH264 == NULL)
{
printf("fope erro [%d%s]\n",__LINE__,__FUNCTION__);
return -1;
}
}
if(fread(g_cach[icach], 1,CACH_LEN,fp_inH264 ) 0)
{
int dataLen = endpoint -startpoint;
if(bufLen < dataLen)
{
printf("recive buffer too short , need %d byte!\n",dataLen);
}
memcpy(buf,g_cach[icach]+startpoint, MIN(dataLen,bufLen));
ioffset = endpoint;
return MIN(dataLen,bufLen);
}
else
{
int oldLen =CACH_LEN -startpoint;
memcpy(g_cach[(icach+1)%2],g_cach[icach]+startpoint, oldLen );
int newLen = 0;
newLen = fread(g_cach[(icach+1)%2]+oldLen, 1,CACH_LEN -(oldLen),fp_inH264);
if(newLen 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 := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= codec_demo
LOCAL_SRC_FILES := NdkMediacodec.cpp
#LOCAL_SHARED_LIBRARIES := libandroid libmediandk liblog //android studio 上这样不行???提示没有定义的模块? 垃圾!!!!! 听说要指定为 platform 21.................
LOCAL_LDLIBS := -lmediandk
LOCAL_LDFLAGS += -pie -fPIE
include $(BUILD_EXECUTABLE)