您当前的位置: 首页 > 学无止境 > 心得笔记 网站首页心得笔记
5. 音视频多线程解码代码演示~1
发布时间:2021-06-01 15:32:44编辑:雪饮阅读()
上篇完成了音視頻解碼器的打開。那麽這篇就正式來完成音視頻的解碼。
解碼的主要流程就是通過av_frame_alloc獲取到幀數據。這些數據可能有音頻幀,也可能有視頻幀。
要區分音頻幀和視頻幀進行不同的解碼器進行幀解碼。
AVPacket用于存储压缩的数据,分别包括有音频压缩数据,视频压缩数据和字幕压缩数据。
AVFrame用于存储解码后的音频或者视频数据。即用於接收AVPacket解碼后的數據。
那麽基於此原理,我們的音視頻多綫程解碼實例:cpp/native-lib.cpp如:
#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>
}
static double r2d(AVRational r){
return r.num==0 | r.den==0 ? 0:(double) r.num/(double)r.den;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ndk_1and_141_MainActivity_stringFromJNI(JNIEnv* env,jobject) {
std::string hello = "Hello from C++";
hello+=avcodec_configuration();
//初始化解封裝
av_register_all();
//初始化網絡
avformat_network_init();
/*
* avcodec_register_all()只有调用了该函数,才能使用编解码器等。
* */
avcodec_register_all();
AVFormatContext *ic=NULL;
//要取一些信息,mp4格式取的比較多
char path[]="/sdcard/1080.mp4";
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!");
也就是多帶了一個%s,很明顯這裏語法在c中是有問題的,可是編譯過程時候沒有報錯。
日志貓中報錯信息讓我一度懷疑自己的arm64位動態鏈接庫編譯錯了呢。。。
*/
LOGW("avformat_open_input success!");
/*
duration方法:
mp4格式可以直接獲取到時長
返回類型是int64_t:
int64_t 是标准C ++类型,用于完全64位的有符号整数。 int64 不是标准类型。
第一个C ++标准没有固定宽度类型。在将 int64_t 添加到标准C ++之前,不同的编译器都实现了64位类型,但它们使用了自己的名称(例如 long long < / code>, __ int64 等。)
那麽因爲long long < / code>,所以這裏int64_t的輸出就是l ld了
*/
//對於flv來説,要先探測獲取下流信息,若不先探測,則獲取的場地可能就是如:-9223372036854775808一樣的負數
re=avformat_find_stream_info(ic,0);
if(re!=0){
LOGW("avformat_find_stream_info failed!");
}
//這裏格式化int64_t的時候千萬記住不是%lld而是%l ld
LOGW("duration=%l ld nb_streams=%d",ic->duration,ic->nb_streams);
int fps = 0;
int videoStream = 0;
int audioStream = 1;
/*
遍历获取AVStream音视频流信息并打印参数
*/
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;
//avg_frame_rate:幀率,一個分數,就是fps的分數表示法,分數表示可以有效保護精度損失
fps = r2d(as->avg_frame_rate);
/*
codecpar->width,codecpar->height:视频的宽高,只有视频有
codecpar->codec_id: 獲取解碼器的id號
codecpar->format:格式。对于视频来说指的就是像素格式(YUV420,YUV422...),对于音频来说,指的就是音频的采样格式。
*/
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;
/*
codecpar->sample_rate:樣本率
codecpar->channels:声道数
codecpar->format:格式。对于视频来说指的就是像素格式(YUV420,YUV422...),对于音频来说,指的就是音频的采样格式。
*/
LOGW("sample_rate=%d channels=%d sample_format=%d",
as->codecpar->sample_rate,
as->codecpar->channels,
as->codecpar->format
);
}
}
/*
獲取音頻流信息
第一個參數:AVStream的實例
第二個參數type:音頻或視頻流類型
第三個參數wanted_stream_nb是指定索引,這裏沒有必要,所以就為-1
第四個參數related_stream,同一節目組的相關流信息,這裏也沒有節目組,所以也就設置為-1
第五個參數decoder_ret 這個參數的作用我們要單獨做
第五個參數flags 目前還沒有用
*/
audioStream=av_find_best_stream(ic,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);
LOGW("av_find_best_stream audioStream=%d",audioStream);
//软解码器-視頻解碼器
//avcodec_find_decoder:获取解码器
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());
}
//解码器初始化,avcodec_alloc_context3方法用于获取编解码器上下文信息
AVCodecContext *vc=avcodec_alloc_context3(codec);
//avcodec_parameters_to_context()方法用于将流的参数(stream->codecpar)复制到解码器中,否则某些流可能无法正常解码。
avcodec_parameters_to_context(vc,ic->streams[videoStream]->codecpar);
//编解码时的线程数量,由用户设置,与CPU核心数有关,最佳效果一般设置为CPU核心数*2.
vc->thread_count=1;
//打开解码器
/*
* avcodec_open2()各个参数的含义:
avctx:需要初始化的AVCodecContext。
codec:输入的AVCodec
options:一些选项。例如使用libx264编码的时候,“preset”,“tune”等都可以通过该参数设置
这里用不到codec和options,所以就填写0吧
* */
re=avcodec_open2(vc,0,0);
if(re!=0){
LOGW("avcodec_open2 video failed!");
return env->NewStringUTF(hello.c_str());
}
//软解码器-音頻解碼器
//avcodec_find_decoder:获取解码器
AVCodec *acodec=avcodec_find_decoder(ic->streams[audioStream]->codecpar->codec_id);
//硬解码(先看软解码,硬解码另外再做)
//codec=avcodec_find_decoder_by_name("h264_mediacodec");
if(!acodec){
LOGW("avcodec_find_audio failed!");
return env->NewStringUTF(hello.c_str());
}
//解码器初始化,avcodec_alloc_context3方法用于获取编解码器上下文信息
AVCodecContext *ac=avcodec_alloc_context3(acodec);
//avcodec_parameters_to_context()方法用于将流的参数(stream->codecpar)复制到解码器中,否则某些流可能无法正常解码。
avcodec_parameters_to_context(ac,ic->streams[audioStream]->codecpar);
//编解码时的线程数量,由用户设置,与CPU核心数有关,最佳效果一般设置为CPU核心数*2.
ac->thread_count=1;
//打开解码器
/*
* avcodec_open2()各个参数的含义:
avctx:需要初始化的AVCodecContext。
codec:输入的AVCodec
options:一些选项。例如使用libx264编码的时候,“preset”,“tune”等都可以通过该参数设置
这里用不到codec和options,所以就填写0吧
* */
re=avcodec_open2(ac,0,0);
if(re!=0){
LOGW("avcodec_open2 audio failed!");
return env->NewStringUTF(hello.c_str());
}
/*
讀取幀數據
av_packet_alloc():分配一个结构体大小的内存
av_read_frame():获取视频的一帧,不存在半帧说法。但可以获取音频的若干帧。
av_seek_frame():
函數原型:
int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp,int flags);
参数说明:
s:操作上下文;
stream_index:基本流索引,表示当前的seek是针对哪个基本流,比如视频或者音频等等。
timestamp:要seek的时间点,以time_base或者AV_TIME_BASE为单位。單位毫秒。
Flags:seek标志,可以设置为按字节,在按时间seek时取该点之前还是之后的关键帧,以及不按关键帧seek等
*/
AVPacket *pkt=av_packet_alloc();
/*
av_frame_alloc:分配AVFrame并将其字段设置为默认值。主要该函数只分配AVFrame的空间,它的data字段的指定的buffer需要其它函数分配。
*/
AVFrame *frame = av_frame_alloc();
for(;;){
int re=av_read_frame(ic,pkt);
if(re!=0){
LOGW("讀取到結尾処!");
/*
這裏跳轉到第20秒処
AVSEEK_FLAG_BACKWARD表示所跳的時間點如果沒有幀则向前找第一个:若你设置seek时间为1秒,但是只有0秒和2秒上才有I帧,则时间从0秒开始。
AVSEEK_FLAG_FRAME:若你设置seek时间为1秒,但是只有0秒和2秒上才有I帧,则时间从2秒开始。是基于帧数量快进.
AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME:既往後找,又找關鍵幀
*/
int pos=20*r2d(ic->streams[videoStream]->time_base);
av_seek_frame(ic,videoStream,pos,AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME);
continue;
}
// LOGW("stream =%d size=%d pts=%l ld flag=%d",pkt->stream_index,pkt->size,pkt->pts,pkt->flags);
/*
av_packet_unref():释放空间,减少引用计数:
对AVPacket缓冲区的引用计数-1.(内部调用 atomic_fetch_add_explicit(…, -1, …)),如果引用为个数为1,将释放data缓冲区;
将其余信息包字段重置为它们的默认值。
例如在编码时,循环处理压缩数据,每循环一次都会分配AVPacket->data来存储压缩后的数据(如 avcodec_receive_packet,内部使用 av_new_packet ),
处理完压缩数据之后,并且在进入下一次循环之前,记得使用 av_packet_unref 来释放已经分配的AVPacket->data缓冲区。
*/
//av_packet_unref(pkt);
//判斷流進行不同的解碼器上下文初始化
AVCodecContext *cc = vc;
if(pkt->stream_index == audioStream){
cc=ac;
}
//发送到线程中解码,发送数据到ffmepg,放到解码队列中
re = avcodec_send_packet(cc,pkt);
//清理
int p = pkt->pts;
//avpacket 解码和解封装是2个线程,如果解封装后,调用此函数后,会将avpacket的引用计数加1 或者复制一份(没有计数引用)。因此在调用了后,释放掉 avpacket。
av_packet_unref(pkt);
if(re != 0)
{
LOGW("avcodec_send_packet failed!");
continue;
}
//不斷的從當前解碼器上下文中解碼數據到解碼后用於存放AVPacket解碼后數據的數據結構AVFrame
for(;;)
{
re = avcodec_receive_frame(cc,frame);
if(re !=0)
{
//LOGW("avcodec_receive_frame failed!");
break;
}
//%lld:即long long=64bit。
LOGW("avcodec_receive_frame %lld",frame->pts);
}
}
avformat_close_input(&ic);
return env->NewStringUTF(hello.c_str());
}
然後編譯部署運行到如魅族16T中效果如:
好,現在就是音視頻的解碼就都完成了。
关键字词:解碼,音視頻,多綫程