[Cmake-Android音视频]ffmpeg3.4实现解封装
程序员文章站
2022-07-13 12:55:15
...
1.解封装流程图
2.函数介绍
av_register_all()
注册所有的解封装格式,也可以根据不同的封装格式,单个注册。
avformat_network_init()
注册网络,如rtsp,http
avformat_open_input(...)
打开输入文件,可以是本地视频文件,也可以是网络链接。
在打开网络链接的时候,该函数默认是阻塞的,遇到下列情况:
- 网络不稳定
- 服务器响应比较慢
- 直播流不存在或者没有数据
会导致该函数长时间不返回。
我们可以通过设置timeout超时时间,或者是设置interrupt_callback定义返回机制。
设置超时
AVFormatContext *ic = NULL;
//设置超时时间,不同的协议设置关键字不一样
AVDictionary *options = 0;
//设置rtsp超时 (in microseconds)
av_dict_set(&options, "stimeout", "5000000", 0); //单位微秒
//设置tcp or udp,默认一般优先tcp再尝试udp
av_dict_set(&opts, "rtsp_transport", m_bIsTcp ? "tcp" : "udp", 0);
//设置http udp超时
av_dict_set(&options, "timeout", "5000000", 0); //单位微秒
int re = avformat_open_input(&ic, path, 0, &options);
设置回调
// 回调函数的参数,用了时间
typedef struct {
time_t lasttime;
} Runner;
// 回调函数
//回调函数中返回1,则代表ffmpeg结束阻塞可以将操纵权交给用户线程并返回错误码
//回调函数中返回0,则代表ffmpeg继续阻塞直到ffmpeg正常工作为止
static int interrupt_callback(void *p) {
Runner *r = (Runner *)p;
if (r->lasttime > 0) {
if (time(NULL) - r->lasttime > 8) {
// 等待超过8s则中断
return 1;
}
}
return 0;
}
// usage
Runner input_runner = {0};
AVFormatContext *ifmt_ctx = avformat_alloc_context();
ifmt_ctx->interrupt_callback.callback = interrupt_callback;
ifmt_ctx->interrupt_callback.opaque = &input_runner;
input_runner.lasttime = time(NULL);
// 调用之前初始化时间
ret = avformat_open_input(&ifmt_ctx, url, NULL, NULL);
if(ret < 0) {
// error
}
avformat_find_stream_info(...)
探测获取封装格式的上下文信息。
在一些格式当中没有头部信息,如flv,h264,mpeg,调用avformat_open_input()在打开文件之后会没有参数,也就无法获取到里面的信息。这个时候就可以调用此函数,因为它会试着去探测文件的格式,但是如果格式当中没有头部信息,那么它只能获取到编码、宽高这些信息,还是无法获得总时长。如果总时长无法获取到,那么需要把整个文件读一遍,计算一下它的总帧数。
avformat_find_stream_info(ic, 0)
av_find_best_stream(...)
获取音视频流索引
int videoStream = 0;
int audioStream = 1;
//获取音频流索引
audioStream = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
//获取视频流索引
videoStream = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
av_read_frame(...)
读取码流中的音频若干帧或者视频一帧,该函数也是阻塞的,可以通过设置超时或者是回调函数让函数立即返回。
使用FFmpeg的av_read_frame函数后,每读完一个packet,必须调用av_packet_unref函数进行内存释放,否则会导致内存释泄漏
// 返回值小于0代表错误或者读完了
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
3.关键结构体介绍
AVFormatContext 封装格式上下文
// I/O context.自定义格式读或者从内存读可用
AVIOContext *pb;
// 输入的文件名
char filename[1024];
// 流的数量
unsigned int nb_streams;
//音频视频字幕流
AVStream **streams;
// 总时长(单位:微秒us,转换为秒需要除以1000000)
int64_t duration;
// 比特率 bit/s,网络适应的时候会用
int64_t bit_rate;
//释放之前在动态链接库中申请的空间,并置0
void avformat_close_input(AVFormatContext **s);
AVStream 存储解码前的音视频信息
//时间基数,通过分子分母计算 (double) r.num / (double) r.den
AVRational time_base;
//通过time_base来计算
//duration * ((double)time_base.num / (double)time_base.den) 秒
//这里面的时长在有些格式里面没有,以AVFormatContext里面的为准
int64_t duration;
//帧率(注:对视频来说,这个挺重要的)
AVRational avg_frame_rate;
//附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面
AVPacket attached_pic;
//音视频参数
AVCodecParameters *codecpar;
AVCodecParameters 音视频参数
//时间基数,通过分子分母计算 (double) r.num / (double) r.den
AVRational time_base;
//通过time_base来计算
//duration * ((double)time_base.num / (double)time_base.den) 秒
//这里面的时长在有些格式里面没有,以AVFormatContext里面的为准
int64_t duration;
//帧率(注:对视频来说,这个挺重要的)
AVRational avg_frame_rate;
//附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面
AVPacket attached_pic;
//音视频参数
AVCodecParameters *codecpar;
AVCodecParameters 音视频参数
// 标志是否是音频还是视频
enum AVMediaType codec_type;
// 对应的编码格式 h264 mpeg4等
enum AVCodecID codec_id;
// 音视频不一样
//音频 采样格式 enum AVSampleFormat
//视频 像素格式 enum AVPixelFormat
int format;
//视频宽高
int width;
int height;
//升到数
int channels;
//采样率
int sample_rate;
AVPacket 存储解码后的数据
// 显示时间戳
int64_t pts;
// 解码时间戳
int64_t dts;
//解码后的数据
uint8_t *data;
int size;
4.解封装关键代码
//把分数转换成浮点数
static double r2d(AVRational r)
{
return (r.num == 0 || r.den == 0) ? 0 : (double)r.num / (double)r.den;
}
char path[] = "/sdcard/v1080.mp4";
//初始化解封装
av_register_all();
//初始化网络
avformat_network_init();
AVFormatContext *ic = NULL;
//设置超时
// AVDictionary *options = NULL;
// av_dict_set(&options, "stimeout", "3000000", 0);
//打开文件
int re = avformat_open_input(&ic, path, 0, 0);
if (re != 0)
{
LOGI("avformat_open_input failed! %s", av_err2str(re));
}
LOGI("duration = %lld", ic->duration);
//获取封装格式的相关信息
re = avformat_find_stream_info(ic, 0);
if (re != 0)
{
LOGI("avformat_find_stream_info! %s", av_err2str(re));
}
int fps = 0;
int videoStream = 0;
int audioStream = 1;
//获取音频信息
audioStream = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
AVStream *aStream = ic->streams[audioStream];
LOGI("音频流 %d", audioStream);
LOGI("sample_rate = %d, channels = %d, sample_format = %d",
aStream->codecpar->sample_rate,
aStream->codecpar->channels,
aStream->codecpar->format);
//获取视频信息
videoStream = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
AVStream *vStream = ic->streams[videoStream];
LOGI("视频流 %d", vStream);
fps = r2d(vStream->avg_frame_rate);
LOGI("fps = %d, width = %d, height = %d, codecId = %d",
fps, vStream->codecpar->width,
vStream->codecpar->height,
vStream->codecpar->codec_id);
//关闭AVFormatContext
avformat_close_input(&ic);
上一篇: oracle 实现插入自增列
下一篇: HttpClient