FFmpeg(三) 编解码相关函数理解
一、编解码基本流程
主要流程:
打开视频解码器(音频一样)
软解码、硬解码
进行编解码
下面先来看打开视频解码器
①avcodec_register_all()//初始化解码
②先找到解码器、
找解码器(软解码):AVCodec *codec = avcodec_find_decoder(stream.codecparcodec_id); 从AVStream中根据codec_id取出解码器
找解码器(硬解码):AVCodec *codec = avcodec_find_decoder_byname("h264_mediacodec "); 从通过名字获取解码器
③解码器上下文
AVCodecContext *cc = avcodec_alloc_context3(codec ); //参数为上面找到的解码器
④把AVStream中的参数复制到我们的AVCodecContext当中
avcodec_parameters_to_context(cc,stream );//stream为音频或者视频的流信息,cc为解码器的上下文
设置线程为1
codec->thread_count = 1
⑤打开解码器
int re = avcodec_open2(cc,0,0);//cc为解码器上下文,返回值 re != 0 则失败
软解码(发送,然后接受数据)
①AVFrame
空间分配 AVFrame *frame =av_frame_alloc() ; //分配空间并初始化
空间释放 void av_frame_free(AVFrame **frame) ;
void av_frame_unref(AVFrame *frame);
int av_frame_ref(AVFrame *dst , const AVFrame *src); // 释放对象本身空间
复制 AVframe *av_frame_clone(const AVFrame *src); //
②早期版本提供两个av_codec_video()和av_codec_audio()
新版本是这两个:avcodec_send_packet() 和avcodec_receive_frame()
解码已经放到后台,以前是单线程在内部等待解码。
现在是多线程:两个步骤 :a、发到发到解码队列;b、接收(接收的时候要接受多次)
//发送到线程解码 int re = avcodec_send_packet(AVCodecContext *acctx , const AVPacket *avpkt); //清理 av_packet_unref(pkt); //把packet写到解码队列 if(re != 0 ) 失败 return //开始接收数据,把解码成功的frame取出来 int re = avcodec_receive_frame( AVCodecContext *acctx ,AVFrame *frame); if(re !=0) 失败 return
FFmpeg调用MediaCodec实现硬解码
① 需要C++调用java ,在C++代码中实现一个函数,Java在执行的时候会自己调用这个函数,不用去调用。
jnit JNI_OnLoad(JavaVM *vm, void *res){ av_jni_set_java_vm(vm , 0); return JNI_VERSION_1_4; }
②找解码器(硬解码):AVCodec *codec = avcodec_find_decoder_byname("h264_mediacodec "); 从通过名字获取解码器
③然后开始解码和软解码一样
二、相关函数解析
①avcodec_register_all()//初始化解码
②avcodec_find_decoder(stream.codecparcodec_id);从AVStream中根据codec_id取出解码器
③avcodec_find_decoder_byname("h264_mediacodec "); 从通过名字获取解码器
④avcodec_alloc_context3(codec ); //,得到解码器上下文、参数为上面找到的解码器
⑤avcodec_parameters_to_context(cc,stream );// 把AVStream中的参数复制到我们的AVCodecContext当中, stream为音频或者视频的流信息,cc为解码器的上下文
⑥int re = avcodec_open2(cc,0,0);//cc为解码器上下文,返回值 re != 0 则失败
#include <jni.h> #include <string> #include <android/log.h> #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,"testff",__VA_ARGS__) extern "C"{ #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavcodec/jni.h> } #include<iostream> using namespace std; static double r2d(AVRational r) { return r.num==0||r.den == 0 ? 0 :(double)r.num/(double)r.den; } //当前时间戳 clock long long GetNowMs() { struct timeval tv; gettimeofday(&tv,NULL); int sec = tv.tv_sec%360000; long long t = sec*1000+tv.tv_usec/1000; return t; } extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm,void *res) { av_jni_set_java_vm(vm,0); return JNI_VERSION_1_4; } extern "C" JNIEXPORT jstring JNICALL Java_aplay_testffmpeg_MainActivity_stringFromJNI( JNIEnv *env, jobject /* this */) { std::string hello = "Hello from C++ "; hello += avcodec_configuration(); //初始化解封装 av_register_all(); //初始化网络 avformat_network_init(); avcodec_register_all(); //打开文件 AVFormatContext *ic = NULL; char path[] = "/sdcard/1080.mp4"; //char path[] = "/sdcard/video.flv"; int re = avformat_open_input(&ic,path,0,0); if(re != 0) { LOGW("avformat_open_input failed!:%s",av_err2str(re)); return env->NewStringUTF(hello.c_str()); } LOGW("avformat_open_input %s success!",path); //获取流信息 re = avformat_find_stream_info(ic,0); if(re != 0) { LOGW("avformat_find_stream_info failed!"); } LOGW("duration = %lld nb_streams = %d",ic->duration,ic->nb_streams); int fps = 0; int videoStream = 0; int audioStream = 1; for(int i = 0; i < ic->nb_streams; i++) { AVStream *as = ic->streams[i]; if(as->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { LOGW("视频数据"); videoStream = i; fps = r2d(as->avg_frame_rate); LOGW("fps = %d,width=%d height=%d codeid=%d pixformat=%d",fps, as->codecpar->width, as->codecpar->height, as->codecpar->codec_id, as->codecpar->format ); } else if(as->codecpar->codec_type ==AVMEDIA_TYPE_AUDIO ) { LOGW("音频数据"); audioStream = i; LOGW("sample_rate=%d channels=%d sample_format=%d", as->codecpar->sample_rate, as->codecpar->channels, as->codecpar->format ); } } //ic->streams[videoStream]; //获取音频流信息 audioStream = av_find_best_stream(ic,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0); LOGW("av_find_best_stream audioStream = %d",audioStream); ////////////////////////////////////////////////////////// //打开视频解码器 //软解码器 AVCodec *codec = avcodec_find_decoder(ic->streams[videoStream]->codecpar->codec_id); //硬解码 codec = avcodec_find_decoder_by_name("h264_mediacodec"); if(!codec) { LOGW("avcodec_find failed!"); return env->NewStringUTF(hello.c_str()); } //解码器初始化 AVCodecContext *vc = avcodec_alloc_context3(codec); avcodec_parameters_to_context(vc,ic->streams[videoStream]->codecpar); vc->thread_count = 8; //打开解码器 re = avcodec_open2(vc,0,0); //vc->time_base = ic->streams[videoStream]->time_base; LOGW("vc timebase = %d/ %d",vc->time_base.num,vc->time_base.den); if(re != 0) { LOGW("avcodec_open2 video failed!"); return env->NewStringUTF(hello.c_str()); } ////////////////////////////////////////////////////////// //打开音频解码器 //软解码器 AVCodec *acodec = avcodec_find_decoder(ic->streams[audioStream]->codecpar->codec_id); //硬解码 //codec = avcodec_find_decoder_by_name("h264_mediacodec"); if(!acodec) { LOGW("avcodec_find failed!"); return env->NewStringUTF(hello.c_str()); } //解码器初始化 AVCodecContext *ac = avcodec_alloc_context3(acodec); avcodec_parameters_to_context(ac,ic->streams[audioStream]->codecpar); ac->thread_count = 8; //打开解码器 re = avcodec_open2(ac,0,0); if(re != 0) { LOGW("avcodec_open2 audio failed!"); return env->NewStringUTF(hello.c_str()); } //读取帧数据 AVPacket *pkt = av_packet_alloc(); AVFrame *frame = av_frame_alloc(); long long start = GetNowMs(); int frameCount = 0; for(;;) { //超过三秒 if(GetNowMs() - start >= 3000) { LOGW("now decode fps is %d",frameCount/3); start = GetNowMs(); frameCount = 0; } int re = av_read_frame(ic,pkt); if(re != 0) { LOGW("读取到结尾处!"); int pos = 20 * r2d(ic->streams[videoStream]->time_base); av_seek_frame(ic,videoStream,pos,AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME ); continue; } //只测试视频 /*if(pkt->stream_index !=videoStream) { continue; }*/ //LOGW("stream = %d size =%d pts=%lld flag=%d", // pkt->stream_index,pkt->size,pkt->pts,pkt->flags //); AVCodecContext *cc = vc; if(pkt->stream_index == audioStream) cc=ac; //发送到线程中解码 re = avcodec_send_packet(cc,pkt); //清理 int p = pkt->pts; av_packet_unref(pkt); if(re != 0) { LOGW("avcodec_send_packet failed!"); continue; } for(;;) { re = avcodec_receive_frame(cc,frame); if(re !=0) { //LOGW("avcodec_receive_frame failed!"); break; } //LOGW("avcodec_receive_frame %lld",frame->pts); //如果是视频帧 if(cc == vc) { frameCount++; } } ////////////////////// } //关闭上下文 avformat_close_input(&ic); return env->NewStringUTF(hello.c_str()); } extern "C" JNIEXPORT jboolean JNICALL Java_aplay_testffmpeg_MainActivity_Open(JNIEnv *env, jobject instance, jstring url_, jobject handle) { const char *url = env->GetStringUTFChars(url_, 0); // TODO FILE *fp = fopen(url,"rb"); if(!fp) { LOGW("File %s open failed!",url); } else { LOGW("File %s open succes!",url); fclose(fp); } env->ReleaseStringUTFChars(url_, url); return true; }