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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/adolph_lu/article/details/90708620

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

//读取帧数据,注意释放AVPacket申请的空间
AVPacket *pkt = av_packet_alloc();
for (;;)
{
    int re = av_read_frame(ic, pkt);
    if (re != 0)
    {
        LOGI("读到结尾处后,跳转到第20秒的位置");
        
        //seek到20秒的位置   需要用videoStream的时间基数来计算
        int pos = 20 * r2d(ic->stream[videoStream]->time_base);

        //seek 操作,向后找并且要找到关键帧
        av_seek_frame(ic, videoStream, pos, AVSEEK_FLAG_FRAME|AVSEEK_FLAG_BACKWARD);
    }
    
    //////////其他操作/////////////

    //pkt的内存一定要释放  不然会导致内存泄露
    av_packet_unref(pkt);
    
}

//关闭AVFormatContext
avformat_close_input(&ic);

猜你喜欢

转载自blog.csdn.net/adolph_lu/article/details/90708620