流媒体开发19FFMpeg解封装流程分析

Posted 叮咚咕噜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了流媒体开发19FFMpeg解封装流程分析相关的知识,希望对你有一定的参考价值。

1、理解解封装流程和函数调用
2、duration和time_base的理解

一、前言

本节描述使用ffmpeg解封装,解封装其实就是将flv、mp4等文件中的音频和视频数据剥离出来,MP4拆分成AAC和H264文件等。

二、封装相关函数接口

  • avformat_alloc_context(): 负责申请一个AVFormatContext结构的内存,并进行简单初始化
  • avformat_free_context(): 释放该结构里的所有东西以及该结构本身
  • avformat_close_input(): 关闭解复用器,关闭后就不再需要使用avformat_free_context 进行释放。
  • avformat_open_input(): 打开输入视频文件(参数1可以传入空的AVFormatContext,内部会进行申请
  • avformat_find_stream_info(): 获取音视频文件信息
  • av_read_frame(): 读取音视频包
  • avformat_seek_file(): 定位文件。(如:播放器拖动时间轴,1分钟拖到5分钟位置)
  • av_seek_frame(): 定位文件。(拖动文件两种方式,按照pts和按照文件大小,如一共10分钟10M,拖动到5分钟则是拖动到5M大小位置)
      alloc申请一个AVFormatContext结构的内存,avformat_open_input打开文件将其与AVFormatContext结构体进行关联,之后就可以获取音视频文件的一些信息(大小、时长、编码格式等),然后调用av_read_frame分别读取音频和视频文件。

三、解封装流程

四、区分不同的码流

  • AVMEDIA_TYPE_VIDEO视频流
video_index = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,-1,-1, NULL, 0)
  • AVMEDIA_TYPE_AUDIO音频流
audio_index = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO,-1,-1, NULL, 0)

AVPacket 里面也有一个index的字段,av_read_frame()获取到的AVPacket的index会和上面av_find_best_stream返回的比较,从而知道当前是视频流还是音频流。

五、重点

  • avformat_open_input和avformat_find_stream_info分别用于打开一个流和分析流信息。
  • 在初始信息不足的情况下(比如FLV和H264文件),avformat_find_stream_info接口需要在内部调用read_frame_internal接口读取流数据(音视频帧),然后再分析后,设置核心数据结构AVFormatContext。由于需要读取数据包,avformat_find_stream_info接口会带来很大的延迟。

六、demo实现

音频时长换算(对duration和time_base的理解): duration中保存的是采样点的个数,av_q2d(in_stream->time_base)是每个采样点的时间(1s/采样率)

static inline double av_q2d(AVRational a){
    return a.num / (double) a.den;

if(in_stream->duration != AV_NOPTS_VALUE)
{
	int duration_audio = (in_stream->duration) * av_q2d(in_stream->time_base);
	//将音频总时长转换为时分秒的格式打印到控制台上
	printf("audio duration: %02d:%02d:%02d\\n",duration_audio / 3600, (duration_audio % 3600) / 60, (duration_audio % 60));
}

解封装信息打印:

#include <stdio.h>
#include <libavformat/avformat.h>


int main(int argc, char **argv)
{
    //打开网络流。这里如果只需要读取本地媒体文件,不需要用到网络功能,可以不用加上这一句
//    avformat_network_init();

    const char *default_filename = "believe.mp4";

    char *in_filename = NULL;

    if(argv[1] == NULL)
    {
        in_filename = default_filename;
    }
    else
    {
        in_filename = argv[1];
    }
    printf("in_filename = %s\\n", in_filename);

    //AVFormatContext是描述一个媒体文件或媒体流的构成和基本信息的结构体
    AVFormatContext *ifmt_ctx = NULL;           // 输入文件的demux

    int videoindex = -1;        // 视频索引
    int audioindex = -1;        // 音频索引


    // 打开文件,主要是探测协议类型,如果是网络文件则创建网络链接
	// ifmt_ctx可以为NULL传入,如果是空,avformat_open_input中会去申请内存
    int ret = avformat_open_input(&ifmt_ctx, in_filename, NULL, NULL);
    if (ret < 0)  //如果打开媒体文件失败,打印失败原因
    {
        char buf[1024] = { 0 };
        av_strerror(ret, buf, sizeof(buf) - 1);
        printf("open %s failed:%s\\n", in_filename, buf);
        goto failed;
    }

    ret = avformat_find_stream_info(ifmt_ctx, NULL);
    if (ret < 0)  //如果打开媒体文件失败,打印失败原因
    {
        char buf[1024] = { 0 };
        av_strerror(ret, buf, sizeof(buf) - 1);
        printf("avformat_find_stream_info %s failed:%s\\n", in_filename, buf);
        goto failed;
    }

    //打开媒体文件成功
    printf_s("\\n==== av_dump_format in_filename:%s ===\\n", in_filename);
    av_dump_format(ifmt_ctx, 0, in_filename, 0);
    printf_s("\\n==== av_dump_format finish =======\\n\\n");
    // url: 调用avformat_open_input读取到的媒体文件的路径/名字
    printf("media name:%s\\n", ifmt_ctx->url);
    // nb_streams: nb_streams媒体流数量
    printf("stream number:%d\\n", ifmt_ctx->nb_streams);
    // bit_rate: 媒体文件的码率,单位为bps
    printf("media average ratio:%lldkbps\\n",(int64_t)(ifmt_ctx->bit_rate/1024));
    // 时间
    int total_seconds, hour, minute, second;
    // duration: 媒体文件时长,单位微妙
    total_seconds = (ifmt_ctx->duration) / AV_TIME_BASE;  // 1000us = 1ms, 1000ms = 1秒
    hour = total_seconds / 3600;
    minute = (total_seconds % 3600) / 60;
    second = (total_seconds % 60);
    //通过上述运算,可以得到媒体文件的总时长
    printf("total duration: %02d:%02d:%02d\\n", hour, minute, second);
    printf("\\n");
    /*
     * 老版本通过遍历的方式读取媒体文件视频和音频的信息
     * 新版本的FFmpeg新增加了函数av_find_best_stream,也可以取得同样的效果
     */
    for (uint32_t i = 0; i < ifmt_ctx->nb_streams; i++)
    {
        AVStream *in_stream = ifmt_ctx->streams[i];// 音频流、视频流、字幕流
        //如果是音频流,则打印音频的信息
        if (AVMEDIA_TYPE_AUDIO == in_stream->codecpar->codec_type)
        {
            printf("----- Audio info:\\n");
            // index: 每个流成分在ffmpeg解复用分析后都有唯一的index作为标识
            printf("index:%d\\n", in_stream->index);
            // sample_rate: 音频编解码器的采样率,单位为Hz
            printf("samplerate:%dHz\\n", in_stream->codecpar->sample_rate);
            // codecpar->format: 音频采样格式
            if (AV_SAMPLE_FMT_FLTP == in_stream->codecpar->format)
            {
                printf("sampleformat:AV_SAMPLE_FMT_FLTP\\n");
            }
            else if (AV_SAMPLE_FMT_S16P == in_stream->codecpar->format)
            {
                printf("sampleformat:AV_SAMPLE_FMT_S16P\\n");
            }
            // channels: 音频信道数目
            printf("channel number:%d\\n", in_stream->codecpar->channels);
            // codec_id: 音频压缩编码格式
            if (AV_CODEC_ID_AAC == in_stream->codecpar->codec_id)
            {
                printf("audio codec:AAC\\n");
            }
            else if (AV_CODEC_ID_MP3 == in_stream->codecpar->codec_id)
            {
                printf("audio codec:MP3\\n");
            }
            else
            {
                printf("audio codec_id:%d\\n", in_stream->codecpar->codec_id);
            }
            // 音频总时长,单位为秒。注意如果把单位放大为毫秒或者微妙,音频总时长跟视频总时长不一定相等的
            if(in_stream->duration != AV_NOPTS_VALUE)
            {
                int duration_audio = (in_stream->duration) * av_q2d(in_stream->time_base);
                //将音频总时长转换为时分秒的格式打印到控制台上
                printf("audio duration: %02d:%02d:%02d\\n",
                       duration_audio / 3600, (duration_audio % 3600) / 60, (duration_audio % 60));
            }
            else
            {
                printf("audio duration unknown");
            }

            printf("\\n");

            audioindex = i; // 获取音频的索引
        }
        else if (AVMEDIA_TYPE_VIDEO == in_stream->codecpar->codec_type)  //如果是视频流,则打印视频的信息
        {
            printf("----- Video info:\\n");
            printf("index:%d\\n", in_stream->index);
            // avg_frame_rate: 视频帧率,单位为fps,表示每秒出现多少帧
            printf("fps:%lffps\\n", av_q2d(in_stream->avg_frame_rate));
            if (AV_CODEC_ID_MPEG4 == in_stream->codecpar->codec_id) //视频压缩编码格式
            {
                printf("video codec:MPEG4\\n");
            }
            else if (AV_CODEC_ID_H264 == in_stream->codecpar->codec_id) //视频压缩编码格式
            {
                printf("video codec:H264\\n");
            }
            else
            {
                printf("video codec_id:%d\\n", in_stream->codecpar->codec_id);
            }
            // 视频帧宽度和帧高度
            printf("width:%d height:%d\\n", in_stream->codecpar->width,
                   in_stream->codecpar->height);
            //视频总时长,单位为秒。注意如果把单位放大为毫秒或者微妙,音频总时长跟视频总时长不一定相等的
            if(in_stream->duration != AV_NOPTS_VALUE)
            {
                int duration_video = (in_stream->duration) * av_q2d(in_stream->time_base);
                printf("video duration: %02d:%02d:%02d\\n",
                       duration_video / 3600,
                       (duration_video % 3600) / 60,
                       (duration_video % 60)); //将视频总时长转换为时分秒的格式打印到控制台上
            }
            else
            {
                printf("video duration unknown");
            }

            printf("\\n");
            videoindex = i;
        }
    }

    AVPacket *pkt = av_packet_alloc();

    int pkt_count = 0;
    int print_max_count = 10;
    printf("\\n-----av_read_frame start\\n");
    while (1)
    {
        ret = av_read_frame(ifmt_ctx, pkt);
        if (ret < 0)
        {
            printf("av_read_frame end\\n");
            break;
        }

        if(pkt_count++ < print_max_count)
        {
            if (pkt->stream_index == audioindex)
            {
                printf("audio pts: %lld\\n", pkt->pts);
                printf("audio dts: %lld\\n", pkt->dts);
                printf("audio size: %d\\n", pkt->size);
                printf("audio pos: %lld\\n", pkt->pos);
                printf("audio duration: %lf\\n\\n",
                       pkt->duration * av_q2d(ifmt_ctx->streams[audioindex]->time_base));
            }
            else if (pkt->stream_index == videoindex)
            {
                printf("video pts: %lld\\n", pkt->pts);
                printf("video dts: %lld\\n", pkt->dts);
                printf("video size: %d\\n", pkt->size);
                printf("video pos: %lld\\n", pkt->pos);
                printf("video duration: %lf\\n\\n",
                       pkt->duration * av_q2d(ifmt_ctx->streams[videoindex]->time_base));
            }
            else
            {
                printf("unknown stream_index:\\n", pkt->stream_index);
            }
        }
			
		//和av_read_frame对应,av_read_frame中申请内存,但是释放需要我们外部释放
        av_packet_unref(pkt);
    }

    if(pkt)
        av_packet_free(&pkt);
failed:
    if(ifmt_ctx)
        avformat_close_input(&ifmt_ctx);


    getchar(); //加上这一句,防止程序打印完信息马上退出
    return 0;
}

以上是关于流媒体开发19FFMpeg解封装流程分析的主要内容,如果未能解决你的问题,请参考以下文章

流媒体开发19FFMpeg解封装流程分析

流媒体开发19FFMpeg解封装流程分析

FFmpeg之ffplay源码简要分析

FFmpeg之ffplay源码简要分析

FFmpeg解封装解码音频和视频(分别使用OpenGL和OpenAL播放)

音视频开发之旅(60) -调试分析FFmpeg (解封装部分的)常用结构体