三 音视频开发 —— AVStream

3.2 AVStream

3.2.1 对比

AVPacketAVStream 是 FFmpeg 中两个不同的结构体,分别用于处理音视频数据的帧和表示音视频流的信息。它们在功能和用途上有不同的设计和定位,因此在使用时有各自的优势。

  1. AVPacket:

    • AVPacket 结构体用于存储编解码器产生的压缩后的音视频数据,即压缩后的帧。它包含了压缩数据的指针、大小、时间戳等信息。
    • 主要用于在音视频的编码和解码过程中传递数据。在解封装(Demuxing)阶段,你会使用 av_read_frame 从文件或网络流中读取 AVPacket
    • 在封装(Muxing)阶段,你将通过编码器得到的 AVFrame 编码为 AVPacket,再写入文件。
  2. AVStream:

    • AVStream 结构体代表了媒体文件中的一个音视频流,包含了该流的详细信息,比如编解码器的参数、时基信息等。
    • 主要用于表示文件中的媒体流的属性,在文件的打开和读取阶段,你可以通过 AVStream 获取有关音视频流的信息,例如编码器类型、帧率、分辨率等。
    • 在解封装阶段,AVStream 用于表示文件中的不同音视频流,你可以根据需要处理这些流。

简而言之,AVPacket 主要用于处理音视频数据的帧,而 AVStream 主要用于表示音视频流的信息。它们分别服务于不同的层面,AVPacket 在数据处理阶段更为重要,而 AVStream 在文件的元数据表示和读取阶段更为关键。

虽然你可以使用 AVStream 来间接访问 AVPacket,但直接使用 AVPacket 更直观,因为它包含了处理帧数据所需的所有信息。在处理音视频数据的过程中,通常会频繁使用 AVPacket 来传递、处理和释放数据。

3.2.2 属性

AVStream 功能
int index 流在avformatContext的索引序号,创建stream时候,由ffmpeg库自动分配,无需手动设置
id
AVCodecContext *codec
void *priv_data
AVRational time_base 时间基
int64_t duration 流总长时间戳
nt64_t nb_frames
AVCodecParameters *codecpar 编码器参数
AVStream->codecpar 功能
AVMEDIA_TYPE_AUDIO == in_stream->codecpar->codec_type 音频
sample_rate 音编码器采样率,Hz
format (AV_SAMPLE_FMT_FLTP/ AV_SAMPLE_FMT_S16P 音频采样格式
channels 音频信道数目
codec_id (AV_CODEC_ID_AAC\ AV_CODEC_ID_MP3 音频压缩编码格式
profile 用于配置adts头部信息
AVMEDIA_TYPE_VIDEO == in_stream->codecpar->codec_type 视频
codec_id (AV_CODEC_ID_MPEG4\ AV_CODEC_ID_h264 视频压缩编码格式
width height 视频宽高
  • codec_type类型

    objectivec 复制代码
     enum AVMediaType {
         AVMEDIA_TYPE_UNKNOWN = -1,  ///< 错误
         AVMEDIA_TYPE_VIDEO,
         AVMEDIA_TYPE_AUDIO,
         AVMEDIA_TYPE_DATA,          ///< Opaque data information usually continuous
         AVMEDIA_TYPE_SUBTITLE,      /// 字母
         AVMEDIA_TYPE_ATTACHMENT,    /// 音频专辑封面图
         AVMEDIA_TYPE_NB
     };
     ​
  • codec_id类型

    objectivec 复制代码
     // 音视频编码器类型
     enum AVCodecID {
         AV_CODEC_ID_NONE,
     ​
         /* video codecs */
         AV_CODEC_ID_MPEG1VIDEO,
         AV_CODEC_ID_MPEG2VIDEO, ///< preferred ID for MPEG-1/2 video decoding
         AV_CODEC_ID_H261,
         AV_CODEC_ID_H263,
         AV_CODEC_ID_RV10,
         .
         .
         .
  • format类型

    objectivec 复制代码
     // 音频采样格式
     enum AVSampleFormat {
         AV_SAMPLE_FMT_NONE = -1,
         AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits
         AV_SAMPLE_FMT_S16,         ///< signed 16 bits
         AV_SAMPLE_FMT_S32,         ///< signed 32 bits
         AV_SAMPLE_FMT_FLT,         ///< float
         AV_SAMPLE_FMT_DBL,         ///< double
     ​
         AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar
         AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar
         AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar
         AV_SAMPLE_FMT_FLTP,        ///< float, planar
         AV_SAMPLE_FMT_DBLP,        ///< double, planar
         AV_SAMPLE_FMT_S64,         ///< signed 64 bits
         AV_SAMPLE_FMT_S64P,        ///< signed 64 bits, planar
     ​
         AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically
     };

3.2.3 接口

ini 复制代码
 // 创建流
 AVStream *st = avformat_new_stream(fmt_ctx_, NULL);
scss 复制代码
 // 从编码器拷贝参数信息
 avcodec_parameters_from_context(st->codecpar, codec_ctx);
 av_dump_format(fmt_ctx_, 0, url_.c_str(), 1);

3.2.4 示例

关于打印stream时间的详细解释

scss 复制代码
 #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;        // 音频索引
 ​
 ​
     // 打开文件,主要是探测协议类型,如果是网络文件则创建网络链接
     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;
     }
 ​
     // mp4不需要,flv需要?????
     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_packet_unref(pkt);
     }
 ​
     if(pkt)
         av_packet_free(&pkt);
 failed:
     if(ifmt_ctx)
         avformat_close_input(&ifmt_ctx);
 ​
 ​
     getchar(); //加上这一句,防止程序打印完信息马上退出
     return 0;
 }
 ​
相关推荐
x007xyz1 个月前
前端纯手工绘制音频波形图
前端·音视频开发·canvas
音视频牛哥1 个月前
Android摄像头采集选Camera1还是Camera2?
音视频开发·视频编码·直播
九酒2 个月前
【harmonyOS NEXT 下的前端开发者】WAV音频编码实现
前端·harmonyos·音视频开发
音视频牛哥2 个月前
结合GB/T28181规范探讨Android平台设备接入模块心跳实现
音视频开发·视频编码·直播
哔哩哔哩技术2 个月前
自研点直播转码核心
音视频开发
音视频牛哥2 个月前
Android平台轻量级RTSP服务模块二次封装版调用说明
音视频开发·视频编码·直播
音视频牛哥2 个月前
Android平台RTSP|RTMP直播播放器技术接入说明
音视频开发·视频编码·直播
山雨楼2 个月前
ExoPlayer架构详解与源码分析(15)——Renderer
android·架构·音视频开发
音视频牛哥2 个月前
Windows平台如何实现多路RTSP|RTMP流合成后录像或转发RTMP服务
音视频开发·视频编码·直播
音视频牛哥2 个月前
GB28181设备接入模块和轻量级RTSP服务有什么区别?
音视频开发·视频编码·直播