對于一個視頻文件(mp4格式/flv格式),audio_pkt或者video_pkt是其最基本的數據單元,即視頻文件是由獨立的視頻編碼包或者音頻編碼包組成的。
解復用就是從視頻文件中把視頻包/音頻包單獨讀取出來保存成獨立文件,那么如何得知packet是視頻包還是音頻包呢?有這樣一個結構體:
typedef struct AVPacket {AVBufferRef *buf; // 指向數據緩沖區的指針int64_t pts; // 顯示時間戳int64_t dts; // 解碼時間戳uint8_t *data; // 指向數據緩沖區的指針int size; // 數據緩沖區大小int stream_index; // 數據包所屬的流標簽int flags; // 數據包的標志位AVPacketSideData *side_data; // 側數據數組int side_data_elems; // 側數據數組的元素數量int64_t duration; // 數據包的持續時間int64_t pos; // 數據包在輸入文件中的位置int64_t convergence_duration; // 數據包的收斂持續時間(棄用)
} AVPacket;
AVPacket中的stream_index標記了該包是屬于音頻流還是視頻流,stream_index對應什么值的時候是屬于音頻流/視頻流呢?那就需要解析flv/mp4文件,我們可以通過以下方式獲得視頻流的相關信息:
char* in_filename = "/home/yx/media_file/believe.flv"; // 定義媒體流路徑AVFormatContext *in_file_ctx = NULL; // 媒體流上下文int videoindex = -1; // 視頻索引int audioindex = -1; // 音頻索引int result = avformat_open_input(&in_file_ctx,in_filename,NULL,NULL); // 打開媒體流(將輸入文件與媒體流相關)result = avformat_find_stream_info(in_file_ctx,NULL); // 查找媒體流信息printf("stream number:%d\n",in_file_ctx->nb_streams); // 打印媒體流中流種類個數,一般只有兩個:音頻/視頻for(uint32_t i = 0;i < in_file_ctx->nb_streams; i++) // 遍歷兩個流{AVStream* in_stream = in_file_ctx->streams[i]; // 指定視頻流文件中第i個流if(in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){printf("**********音頻流**********\n");printf("samplerate:%dHz\n",in_stream->codecpar->sample_rate); // 采樣率printf("index:%d\n",in_stream->index); // 媒體流標簽printf("channel number:%d\n",in_stream->codecpar->channels); // 聲道數if(in_stream->codecpar->format == AV_SAMPLE_FMT_FLTP) // 采樣格式printf("sampleformat:AV_SAMPLE_FMT_FLTP\n");else if(in_stream->codecpar->format == AV_SAMPLE_FMT_S16P)printf("sampleformat:AV_SAMPLE_FMT_S16P\n");if(in_stream->codecpar->codec_id == AV_CODEC_ID_AAC) // 打印音頻流編碼格式printf("audio codec:AV_CODEC_ID_AAC\n");else if(in_stream->codecpar->codec_id == AV_CODEC_ID_MP3)printf("audio codec:AV_CODEC_ID_MP3\n");elseprintf("audio codec:%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));}elseprintf("audio duration unknown\n");audioindex = i; // 獲得音頻標簽 }else if(in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){printf("**********視頻流**********\n");printf("fps:%lffps\n",av_q2d(in_stream->avg_frame_rate)); // 幀率printf("index:%d\n",in_stream->index); // 媒體流標簽printf("width:%d,height:%d\n",in_stream->codecpar->width,in_stream->codecpar->height); // 聲道數if(in_stream->codecpar->codec_id = AV_CODEC_ID_MPEG4)printf("video codec:MPEG4\n");else if(in_stream->codecpar->codec_id = AV_CODEC_ID_H264)printf("video codec:H264\n");elseprintf("video codec:%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("video duration: %02d:%02d:%02d\n",duration_audio/3600,(duration_audio % 3600)/60,(duration_audio % 60));}elseprintf("video duration unknown\n");videoindex = i; // 獲得視頻標簽}}
此時我們就獲得了解復用最關鍵的信息:視頻流標簽和音頻流標簽,接下來只需要依次讀取視頻流中的packet,依次判斷AVPacket中的stream_index來區分音頻或者視頻,這里先讀取20個packet進行分析:
AVPacket* pkt = av_packet_alloc();int pkt_count = 0; // 當前是第0個包int print_count = 20; // 最大打印十個包的信息while(pkt_count<=20) // 只解析20個包{result = av_read_frame(in_file_ctx,pkt); // 依次從輸入視頻來讀取包if(result < 0){printf("av_read_frame fail\n");break;}if(pkt_count++ < print_count){if(pkt->stream_index == audioindex){printf("audioindex:%d\n",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(in_file_ctx->streams[audioindex]->time_base));}else if(pkt->stream_index == videoindex){printf("videoindex:%d\n",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(in_file_ctx->streams[videoindex]->time_base));}}av_packet_unref(pkt); // 解析完引用計數-1,自動釋放}
這里我們讀取到視頻包或者音頻包后,打印包的詳細信息:
pts:編碼時間戳,dts:解碼時間戳,size:包的大小,pos:包當前的位置。
每一個包的相關信息讀取之后,調用 av_packet_unref(pkt)使引用計數–,當計數減為0,系統會自動釋放該部分空間。
完整代碼如下:
#include <stdio.h>
#include "libavformat/avformat.h"
void demux_flv()
{char* in_filename = "/home/yx/media_file/believe.flv";printf("輸入文件路徑%s\n",in_filename);AVFormatContext *in_file_ctx = NULL; // 媒體流上下文int videoindex = -1; // 視頻索引int audioindex = -1; // 音頻索引int result = avformat_open_input(&in_file_ctx,in_filename,NULL,NULL); // 打開媒體流(將輸入文件與媒體流相關)if(result < 0)printf("open file fail\n");result = avformat_find_stream_info(in_file_ctx,NULL); // 查找媒體流信息if(result < 0)printf("find stream info fail\n");av_dump_format(in_file_ctx,0,in_filename,0); // 打印輸出媒體流的信息,第1個0表示輸出所有流printf("media name:%s\n",in_file_ctx->url);printf("stream number:%d\n",in_file_ctx->nb_streams); // 只有兩個流:視頻流或者音頻流printf("media average radio:%lldkps\n",(int64_t)(in_file_ctx->bit_rate/1024));int total_seconds,hour,minute,second;total_seconds = (in_file_ctx->duration)/AV_TIME_BASE;hour = total_seconds/3600;minute = (total_seconds % 3600)/60;second = (total_seconds % 60);printf("total duration: %02d:%02d:%02d\n",hour,minute,second);for(uint32_t i = 0;i < in_file_ctx->nb_streams; i++) // 遍歷兩個流{AVStream* in_stream = in_file_ctx->streams[i]; // 指定視頻流文件中第i個流if(in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){printf("**********音頻流**********\n");printf("samplerate:%dHz\n",in_stream->codecpar->sample_rate); // 采樣率printf("index:%d\n",in_stream->index); // 媒體流標簽printf("channel number:%d\n",in_stream->codecpar->channels); // 聲道數if(in_stream->codecpar->format == AV_SAMPLE_FMT_FLTP) // 采樣格式printf("sampleformat:AV_SAMPLE_FMT_FLTP\n");else if(in_stream->codecpar->format == AV_SAMPLE_FMT_S16P)printf("sampleformat:AV_SAMPLE_FMT_S16P\n");if(in_stream->codecpar->codec_id == AV_CODEC_ID_AAC) // 打印音頻流編碼格式printf("audio codec:AV_CODEC_ID_AAC\n");else if(in_stream->codecpar->codec_id == AV_CODEC_ID_MP3)printf("audio codec:AV_CODEC_ID_MP3\n");elseprintf("audio codec:%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));}elseprintf("audio duration unknown\n");audioindex = i; // 獲得音頻標簽}else if(in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){printf("**********視頻流**********\n");printf("fps:%lffps\n",av_q2d(in_stream->avg_frame_rate)); // 幀率printf("index:%d\n",in_stream->index); // 媒體流標簽printf("width:%d,height:%d\n",in_stream->codecpar->width,in_stream->codecpar->height); // 聲道數if(in_stream->codecpar->codec_id = AV_CODEC_ID_MPEG4)printf("video codec:MPEG4\n");else if(in_stream->codecpar->codec_id = AV_CODEC_ID_H264)printf("video codec:H264\n");elseprintf("video codec:%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("video duration: %02d:%02d:%02d\n",duration_audio/3600,(duration_audio % 3600)/60,(duration_audio % 60));}elseprintf("video duration unknown\n");videoindex = i; // 獲得視頻標簽}}printf("====================================\n");AVPacket* pkt = av_packet_alloc();int pkt_count = 0; // 當前是第0個包int print_count = 20; // 最大打印十個包的信息while(pkt_count<=20) // 只解析20個包{result = av_read_frame(in_file_ctx,pkt); // 依次從輸入視頻來讀取包if(result < 0){printf("av_read_frame fail\n");break;}if(pkt_count++ < print_count){if(pkt->stream_index == audioindex){printf("audioindex:%d\n",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(in_file_ctx->streams[audioindex]->time_base));}else if(pkt->stream_index == videoindex){printf("videoindex:%d\n",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(in_file_ctx->streams[videoindex]->time_base));}}av_packet_unref(pkt); // 解析完引用計數-1,自動釋放}
}int main()
{demux_flv();printf("Hello World!\n");return 0;
}