解码器分为音频与视频。视频又分为硬解与软解。
以下是初始化视频解码器:
//视频解码器 软解码器
AVCodec *codec = avcodec_find_decoder(ic->streams[videoStream]->codecpar->codec_id);
//采用视频硬解码器
//codec=avcodec_find_decoder_by_name("h264_mediacodec");
if (!codec) {//判断解码器是否初始化成功
LOGW("avcodec video find failed");
return env->NewStringUTF(hello.c_str());;
}
//获得视频解码器上下文 这里的上下文参数只有一个解码器,然后并无流信息
AVCodecContext *vc = avcodec_alloc_context3(codec);
//将视频流的信息注入到上下文吧,下面的操作跟codec解码器本身已经没什么关系了,主要是用AVCodecContext上下文去处理
avcodec_parameters_to_context(vc, ic->streams[videoStream]->codecpar);
//设置视频解码的线程数 越高越快,但肯定对CPU的消耗就越大
//若采用硬解码,则这里的定义没什么意义
vc->thread_count =8;
//打开解码器
re = avcodec_open2(vc,0,0);
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);
if (!acodec) {//判断解码器是否初始化成功
LOGW("acodec audio find failed");
return env->NewStringUTF(hello.c_str());;
}
//音频解码器初始化
AVCodecContext *ac = avcodec_alloc_context3(acodec);
//将音频流的信息注入到上下文吧,下面的操作跟codec解码器本身已经没什么关系了,主要是用AVCodecContext上下文去处理
avcodec_parameters_to_context(ac, ic->streams[audioStream]->codecpar);
//设置音频解码的线程数 越高越快,但肯定对CPU的消耗就越大
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 联系起来:
//AVPacket 相当于保存了帧信息,可以是保存某一帧,也能是一个数组,保存一个帧集合 所以这里注意清理数据
AVPacket *apt = av_packet_alloc();
//AVFrame其实就是每一帧!
AVFrame *frame=av_frame_alloc();
当 re = av_read_frame(ic, apt); 这个方法执行后,就会将frame注入到apt中。
此时解码关键就是:re=avcodec_send_packet(cc,apt); cc是解码器上下文,对应上面的ac或者vc,此方法是将apt发送到解码线程,进行解码。 这里要注意,如果apt是最后一帧的话,要传一个NULL进去,不然取帧的时候,最后几帧会掉下。
//将其发送到解码线程中 这里传送的是一个AVPacket
re=avcodec_send_packet(cc,apt);
//清理
int p = apt->pts;
//已经将apt发送到了接收线程,所以这里直接清理就可以了。因为接收线程已经又有了一份,
av_packet_unref(apt);
if(re !=0){
LOGW("avcodec_send_packet failed! errorinfo=%s" ,av_err2str(re));
return env->NewStringUTF(hello.c_str());
}
现在将解码出的帧取出来,关键代码为:re =avcodec_receive_frame(cc,frame); 这里的frame不用回收。跟apt还不太一样。
为什么要开一个循环来取呢,因为在解码是avcodec_send_packet这个方法,他可能并不是解一致,就会回调avcodec_receive_frame,可能是解几帧才回调一次,因此这里就采用无限循环avcodec_receive_frame来取内已经解码好的帧。
这里直接取出传送过来的所有帧,由于很可能不止一帧,因此我们开启一个循环来取
for(;;){
re =avcodec_receive_frame(cc,frame);
if(re !=0){
//表示已经取完
break;
}
if(cc=vc){
//表示是视频帧
}
}
当采用硬解码时,需要加上这个方法:
相应的,要引入头文件:
extern "C" {
#include <libavcodec/jni.h>
}
extern "C"
JNIEXPORT
jint JNI_OnLoad(JavaVM *vm,void *res)
{
av_jni_set_java_vm(vm,0);
return JNI_VERSION_1_4;
}
ffmepg调用硬解码,其实并不是在C层调,而是要调用JAVA层的代码。
网友评论