欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[Cmake-Android音视频]ffmpeg3.4实现解封装

程序员文章站 2022-07-13 12:55:15
...

1.解封装流程图

[Cmake-Android音视频]ffmpeg3.4实现解封装

 

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);