FFmpeg编解码demo

阅读: 评论:0

FFmpeg编解码demo

FFmpeg编解码demo

一、 FFmpeg解复用(解封装)Demo

#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 = %sn", in_filename);//AVFormatContext是描述一个媒体文件或媒体流的构成和基本信息的结构体AVFormatContext *ifmt_ctx = NULL;           // 输入文件的demuxint 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:%sn", 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:%sn", 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 =======nn");// url: 调用avformat_open_input读取到的媒体文件的路径/名字printf("media name:%sn", ifmt_ctx->url);// nb_streams: nb_streams媒体流数量printf("stream number:%dn", ifmt_ctx->nb_streams);// bit_rate: 媒体文件的码率,单位为bpsprintf("media average ratio:%lldkbpsn",(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:%02dn", 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:%dn", in_stream->index);// sample_rate: 音频编解码器的采样率,单位为Hzprintf("samplerate:%dHzn", in_stream->codecpar->sample_rate);// codecpar->format: 音频采样格式if (AV_SAMPLE_FMT_FLTP == in_stream->codecpar->format){printf("sampleformat:AV_SAMPLE_FMT_FLTPn");}else if (AV_SAMPLE_FMT_S16P == in_stream->codecpar->format){printf("sampleformat:AV_SAMPLE_FMT_S16Pn");}// channels: 音频信道数目printf("channel number:%dn", in_stream->codecpar->channels);// codec_id: 音频压缩编码格式if (AV_CODEC_ID_AAC == in_stream->codecpar->codec_id){printf("audio codec:AACn");}else if (AV_CODEC_ID_MP3 == in_stream->codecpar->codec_id){printf("audio codec:MP3n");}else{printf("audio codec_id:%dn", 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:%02dn",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:%dn", in_stream->index);// avg_frame_rate: 视频帧率,单位为fps,表示每秒出现多少帧printf("fps:%lffpsn", av_q2d(in_stream->avg_frame_rate));if (AV_CODEC_ID_MPEG4 == in_stream->codecpar->codec_id) //视频压缩编码格式{printf("video codec:MPEG4n");}else if (AV_CODEC_ID_H264 == in_stream->codecpar->codec_id) //视频压缩编码格式{printf("video codec:H264n");}else{printf("video codec_id:%dn", in_stream->codecpar->codec_id);}// 视频帧宽度和帧高度printf("width:%d height:%dn", 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:%02dn",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 startn");while (1){ret = av_read_frame(ifmt_ctx, pkt);if (ret < 0){printf("av_read_frame endn");break;}if(pkt_count++ < print_max_count){if (pkt->stream_index == audioindex){printf("audio pts: %lldn", pkt->pts);printf("audio dts: %lldn", pkt->dts);printf("audio size: %dn", pkt->size);printf("audio pos: %lldn", pkt->pos);printf("audio duration: %lfnn",pkt->duration * av_q2d(ifmt_ctx->streams[audioindex]->time_base));}else if (pkt->stream_index == videoindex){printf("video pts: %lldn", pkt->pts);printf("video dts: %lldn", pkt->dts);printf("video size: %dn", pkt->size);printf("video pos: %lldn", pkt->pos);printf("video duration: %lfnn",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;
}

二、读取媒体文件,将aac帧提取出到本地。

#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>#define ADTS_HEADER_LEN  7;const int sampling_frequencies[] = {96000,  // 0x088200,  // 0x164000,  // 0x248000,  // 0x344100,  // 0x432000,  // 0x524000,  // 0x622050,  // 0x716000,  // 0x812000,  // 0x911025,  // 0xa8000   // 0xb// 0xc d e f是保留的
};int adts_header(char * const p_adts_header, const int data_length,const int profile, const int samplerate,const int channels)
{int sampling_frequency_index = 3; // 默认使用48000hzint adtsLen = data_length + 7;int frequencies_size = sizeof(sampling_frequencies) / sizeof(sampling_frequencies[0]);int i = 0;for(i = 0; i < frequencies_size; i++){if(sampling_frequencies[i] == samplerate){sampling_frequency_index = i;break;}}if(i >= frequencies_size){printf("unsupport samplerate:%dn", samplerate);return -1;}p_adts_header[0] = 0xff;         //syncword:0xfff                          高8bitsp_adts_header[1] = 0xf0;         //syncword:0xfff                          低4bitsp_adts_header[1] |= (0 << 3);    //MPEG Version:0 for MPEG-4,1 for MPEG-2  1bitp_adts_header[1] |= (0 << 1);    //Layer:0                                 2bitsp_adts_header[1] |= 1;           //protection absent:1                     1bitp_adts_header[2] = (profile)<<6;            //profile:profile               2bitsp_adts_header[2] |= (sampling_frequency_index & 0x0f)<<2; //sampling frequency index:sampling_frequency_index  4bitsp_adts_header[2] |= (0 << 1);             //private bit:0                   1bitp_adts_header[2] |= (channels & 0x04)>>2; //channel configuration:channels  高1bitp_adts_header[3] = (channels & 0x03)<<6; //channel configuration:channels 低2bitsp_adts_header[3] |= (0 << 5);               //original:0                1bitp_adts_header[3] |= (0 << 4);               //home:0                    1bitp_adts_header[3] |= (0 << 3);               //copyright id bit:0        1bitp_adts_header[3] |= (0 << 2);               //copyright id start:0      1bitp_adts_header[3] |= ((adtsLen & 0x1800) >> 11);           //frame length:value   高2bitsp_adts_header[4] = (uint8_t)((adtsLen & 0x7f8) >> 3);     //frame length:value    中间8bitsp_adts_header[5] = (uint8_t)((adtsLen & 0x7) << 5);       //frame length:value    低3bitsp_adts_header[5] |= 0x1f;                                 //buffer fullness:0x7ff 高5bitsp_adts_header[6] = 0xfc;      //‭11111100‬       //buffer fullness:0x7ff 低6bits// number_of_raw_data_blocks_in_frame://    表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧。return 0;
}int main(int argc, char *argv[])
{int ret = -1;char errors[1024];char *in_filename = NULL;char *aac_filename = NULL;FILE *aac_fd = NULL;int audio_index = -1;int len = 0;AVFormatContext *ifmt_ctx = NULL;AVPacket pkt;// 设置打印级别av_log_set_level(AV_LOG_DEBUG);if(argc < 3){av_log(NULL, AV_LOG_DEBUG, "the count of parameters should be more than three!n");return -1;}in_filename = argv[1];      // 输入文件aac_filename = argv[2];     // 输出文件if(in_filename == NULL || aac_filename == NULL){av_log(NULL, AV_LOG_DEBUG, "src or dts file is null, plz check them!n");return -1;}aac_fd = fopen(aac_filename, "wb");if (!aac_fd){av_log(NULL, AV_LOG_DEBUG, "Could not open destination file %sn", aac_filename);return -1;}// 打开输入文件if((ret = avformat_open_input(&ifmt_ctx, in_filename, NULL, NULL)) < 0){av_strerror(ret, errors, 1024);av_log(NULL, AV_LOG_DEBUG, "Could not open source file: %s, %d(%s)n",in_filename,ret,errors);return -1;}// 获取解码器信息if((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0){av_strerror(ret, errors, 1024);av_log(NULL, AV_LOG_DEBUG, "failed to find stream information: %s, %d(%s)n",in_filename,ret,errors);return -1;}// dump媒体信息av_dump_format(ifmt_ctx, 0, in_filename, 0);// 初始化packetav_init_packet(&pkt);// 查找audio对应的steam indexaudio_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);if(audio_index < 0){av_log(NULL, AV_LOG_DEBUG, "Could not find %s stream in input file %sn",av_get_media_type_string(AVMEDIA_TYPE_AUDIO),in_filename);return AVERROR(EINVAL);}// 打印AAC级别printf("audio profile:%d, FF_PROFILE_AAC_LOW:%dn",ifmt_ctx->streams[audio_index]->codecpar->profile,FF_PROFILE_AAC_LOW);if(ifmt_ctx->streams[audio_index]->codecpar->codec_id != AV_CODEC_ID_AAC){printf("the media file no contain AAC stream, it's codec_id is %dn",ifmt_ctx->streams[audio_index]->codecpar->codec_id);goto failed;}// 读取媒体文件,并把aac数据帧写入到本地文件while(av_read_frame(ifmt_ctx, &pkt) >=0 ){if(pkt.stream_index == audio_index){char adts_header_buf[7] = {0};adts_header(adts_header_buf, pkt.size,ifmt_ctx->streams[audio_index]->codecpar->profile,ifmt_ctx->streams[audio_index]->codecpar->sample_rate,ifmt_ctx->streams[audio_index]->codecpar->channels);fwrite(adts_header_buf, 1, 7, aac_fd);  // 写adts header , ts流不适用,ts流分离出来的packet带了adts headerlen = fwrite( pkt.data, 1, pkt.size, aac_fd);   // 写adts dataif(len != pkt.size){av_log(NULL, AV_LOG_DEBUG, "warning, length of writed data isn't equal pkt.size(%d, %d)n",len,pkt.size);}}av_packet_unref(&pkt);}failed:// 关闭输入文件if(ifmt_ctx){avformat_close_input(&ifmt_ctx);}if(aac_fd){fclose(aac_fd);}return 0;
}

三、从媒体文件中提取出h264视频帧到本地

#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>static char err_buf[128] = {0};
static char* av_get_err(int errnum)
{av_strerror(errnum, err_buf, 128);return err_buf;
}/*
AvCodecContext->extradata[]中为nalu长度
*   codec_extradata:
*   1, 64, 0, 1f, ff, e1, [0, 18], 67, 64, 0, 1f, ac, c8, 60, 78, 1b, 7e,
*   78, 40, 0, 0, fa, 40, 0, 3a, 98, 3, c6, c, 66, 80,
*   1, [0, 5],68, e9, 78, bc, b0, 0,
*///ffmpeg -i 2018.mp4 -codec copy -bsf:h264_mp4toannexb -f h264 tmp.h264
//ffmpeg 从mp4上提取H264的nalu h
int main(int argc, char **argv)
{AVFormatContext *ifmt_ctx = NULL;int             videoindex = -1;AVPacket        *pkt = NULL;int             ret = -1;int             file_end = 0; // 文件是否读取结束if(argc < 3){printf("usage inputfile outfilen");return -1;}FILE *outfp=fopen(argv[2],"wb");printf("in:%s out:%sn", argv[1], argv[2]);// 分配解复用器的内存,使用avformat_close_input释放ifmt_ctx = avformat_alloc_context();if (!ifmt_ctx){printf("[error] Could not allocate context.n");return -1;}// 根据url打开码流,并选择匹配的解复用器ret = avformat_open_input(&ifmt_ctx,argv[1], NULL, NULL);if(ret != 0){printf("[error]avformat_open_input: %sn", av_get_err(ret));return -1;}// 读取媒体文件的部分数据包以获取码流信息ret = avformat_find_stream_info(ifmt_ctx, NULL);if(ret < 0){printf("[error]avformat_find_stream_info: %sn", av_get_err(ret));avformat_close_input(&ifmt_ctx);return -1;}// 查找出哪个码流是video/audio/subtitlesvideoindex = -1;// 推荐的方式videoindex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);if(videoindex == -1){printf("Didn't find a video stream.n");avformat_close_input(&ifmt_ctx);return -1;}// 分配数据包pkt = av_packet_alloc();av_init_packet(pkt);// 1 获取相应的比特流过滤器//FLV/MP4/MKV等结构中,h264需要h264_mp4toannexb处理。添加SPS/PPS等信息。// FLV封装时,可以把多个NALU放在一个VIDEO TAG中,结构为4B NALU长度+NALU1+4B NALU长度+NALU2+...,// 需要做的处理把4B长度换成00000001或者000001const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");AVBSFContext *bsf_ctx = NULL;// 2 初始化过滤器上下文av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext;// 3 添加解码器属性avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);av_bsf_init(bsf_ctx);file_end = 0;while (0 == file_end){if((ret = av_read_frame(ifmt_ctx, pkt)) < 0){// 没有更多包可读file_end = 1;printf("read file end: ret:%dn", ret);}if(ret == 0 && pkt->stream_index == videoindex){
#if 0int input_size = pkt->size;int out_pkt_count = 0;if (av_bsf_send_packet(bsf_ctx, pkt) != 0) // bitstreamfilter内部去维护内存空间{av_packet_unref(pkt);   // 你不用了就把资源释放掉continue;       // 继续送}av_packet_unref(pkt);   // 释放资源while(av_bsf_receive_packet(bsf_ctx, pkt) == 0){out_pkt_count++;// printf("fwrite size:%dn", pkt->size);size_t size = fwrite(pkt->data, 1, pkt->size, outfp);if(size != pkt->size){printf("fwrite failed-> write:%u, pkt_size:%un", size, pkt->size);}av_packet_unref(pkt);}if(out_pkt_count >= 2){printf("cur pkt(size:%d) only get 1 out pkt, it get %d pktsn",input_size, out_pkt_count);}
#else       // TS流可以直接写入size_t size = fwrite(pkt->data, 1, pkt->size, outfp);if(size != pkt->size){printf("fwrite failed-> write:%u, pkt_size:%un", size, pkt->size);}av_packet_unref(pkt);
#endif}else{if(ret == 0)av_packet_unref(pkt);        // 释放内存}}if(outfp)fclose(outfp);if(bsf_ctx)av_bsf_free(&bsf_ctx);if(pkt)av_packet_free(&pkt);if(ifmt_ctx)avformat_close_input(&ifmt_ctx);printf("finishn");return 0;
}

四、flv解封装

(1)FlvMetaData.cpp

#include "FlvMetaData.h"FlvMetaData::FlvMetaData(uint8_t *meta, unsigned int length) {m_meta = meta;m_length = length;m_duration = 0;m_width = 0;m_height = 0;m_framerate = 0;m_videodatarate = 0;m_audiodatarate = 0;m_videocodecid = 0;m_audiosamplerate = 0;m_audiosamplesize = 0;m_stereo = false;parseMeta();
}FlvMetaData::FlvMetaData(const FlvMetaData& r) {m_length = r.m_length;m_meta = new uint8_t[m_length];memcpy(m_meta, r.m_meta, m_length);m_duration = r.m_duration;m_width = r.m_width;m_height = r.m_height;m_framerate = r.m_framerate;m_videodatarate = r.m_videodatarate;m_audiodatarate = r.m_audiodatarate;m_videocodecid = r.m_videocodecid;m_audiosamplerate = r.m_audiosamplerate;m_audiosamplesize = r.m_audiosamplesize;m_stereo = r.m_stereo;
}FlvMetaData&  FlvMetaData::operator=(const FlvMetaData& r) {if(this == &r) {return *this;}if(m_meta != NULL) {delete m_meta;}m_length = r.m_length;m_meta = new uint8_t[m_length];memcpy(m_meta, r.m_meta, m_length);m_duration = r.m_duration;m_width = r.m_width;m_height = r.m_height;m_framerate = r.m_framerate;m_videodatarate = r.m_videodatarate;m_audiodatarate = r.m_audiodatarate;m_videocodecid = r.m_videocodecid;m_audiosamplerate = r.m_audiosamplerate;m_audiosamplesize = r.m_audiosamplesize;m_stereo = r.m_stereo;return *this;
}FlvMetaData::~FlvMetaData() {if(m_meta != NULL) {delete m_meta;m_meta = NULL;}
}void FlvMetaData::parseMeta() {unsigned int arrayLen = 0;unsigned int offset = TAG_HEAD_LEN + 13;unsigned int nameLen = 0;double numValue = 0;bool boolValue = false;if(m_meta[offset++] == 0x08) {arrayLen |= m_meta[offset++];arrayLen = arrayLen << 8;arrayLen |= m_meta[offset++];arrayLen = arrayLen << 8;arrayLen |= m_meta[offset++];arrayLen = arrayLen << 8;arrayLen |= m_meta[offset++];//cerr << "ArrayLen = " << arrayLen << endl;} else {//TODO:cerr << "metadata format error!!!" << endl;return ;}for(unsigned int i = 0; i < arrayLen; i++) {numValue = 0;boolValue = false;nameLen = 0;nameLen |= m_meta[offset++];nameLen = nameLen << 8;nameLen |= m_meta[offset++];//cerr << "name length=" << nameLen << " ";char name[nameLen + 1];
#if DEBUGprintf("norign n");for(unsigned int i = 0; i < nameLen + 3; i++) {printf("%x ", m_meta[offset + i]);}printf("n");
#endifmemset(name, 0, sizeof(name));memcpy(name, &m_meta[offset], nameLen);name[nameLen + 1] = '';offset += nameLen;//cerr << "name=" << name << " ";
#if DEBUGprintf("memcpyn");for(unsigned int i = 0; i < nameLen; i++) {printf("%x ", name[i]);}printf("n");
#endifswitch(m_meta[offset++]) {case 0x0: //Number typenumValue = hexStr2double(&m_meta[offset], 8);offset += 8;break;case 0x1: //Boolean typeif(offset++ != 0x00) {boolValue = true;}break;case 0x2: //String typenameLen = 0;nameLen |= m_meta[offset++];nameLen = nameLen << 8;nameLen |= m_meta[offset++];offset += nameLen;break;case 0x12: //Long string typenameLen = 0;nameLen |= m_meta[offset++];nameLen = nameLen << 8;nameLen |= m_meta[offset++];nameLen = nameLen << 8;nameLen |= m_meta[offset++];nameLen = nameLen << 8;nameLen |= m_meta[offset++];offset += nameLen;break;//FIXME:default:break;}if(strncmp(name, "duration", 8)	== 0) {m_duration = numValue;} else if(strncmp(name, "width", 5)	== 0) {m_width = numValue;} else if(strncmp(name, "height", 6) == 0) {m_height = numValue;} else if(strncmp(name, "framerate", 9) == 0) {m_framerate = numValue;} else if(strncmp(name, "videodatarate", 13) == 0) {m_videodatarate = numValue;} else if(strncmp(name, "audiodatarate", 13) == 0) {m_audiodatarate = numValue;} else if(strncmp(name, "videocodecid", 12) == 0) {m_videocodecid = numValue;} else if(strncmp(name, "audiosamplerate", 15) == 0) {m_audiosamplerate = numValue;} else if(strncmp(name, "audiosamplesize", 15) == 0) {m_audiosamplesize = numValue;} else if(strncmp(name, "audiocodecid", 12) == 0) {m_audiocodecid = numValue;} else if(strncmp(name, "stereo", 6) == 0) {m_stereo = boolValue;}}
}double FlvMetaData::hexStr2double(const uint8_t* hex, const unsigned int length) {double ret = 0;char hexstr[length * 2];memset(hexstr, 0, sizeof(hexstr));for(unsigned int i = 0; i < length; i++) {sprintf(hexstr + i * 2, "%02x", hex[i]);}sscanf(hexstr, "%llx", (unsigned long long*)&ret);return ret;
}double FlvMetaData::getDuration() {return m_duration;
}double FlvMetaData::getWidth() {return m_width;
}double FlvMetaData::getHeight() {return m_height;
}double FlvMetaData::getFramerate() {return m_framerate;
}double FlvMetaData::getVideoDatarate() {return m_videodatarate;
}double FlvMetaData::getAudioDatarate() {return m_audiodatarate;
}double FlvMetaData::getVideoCodecId() {return m_videocodecid;
}double FlvMetaData::getAudioSamplerate() {return m_audiosamplerate;
}double FlvMetaData::getAudioSamplesize() {return m_audiosamplesize;
}double FlvMetaData::getAudioCodecId() {return m_audiocodecid;
}bool FlvMetaData::getStereo() {return m_stereo;
}

(2) FlvMetaData.h

#ifndef FLVMETADATA_H
#define FLVMETADATA_H#include <cstdint>class FlvMetaData
{public:FlvMetaData(uint8_t *meta, uint32_t length);~FlvMetaData();FlvMetaData(const FlvMetaData&);FlvMetaData& operator=(const FlvMetaData&);double getDuration();double getWidth();double getHeight();double getFramerate();double getVideoDatarate();double getAudioDatarate();double getVideoCodecId();double getAudioCodecId();double getAudioSamplerate();double getAudioSamplesize();bool getStereo();private://convert HEX to doubledouble hexStr2double(const uint8_t* hex, const uint32_t length);void parseMeta();private:uint8_t *m_meta;uint32_t m_length;double m_duration;double m_width;double m_height;double m_framerate;double m_videodatarate;double m_audiodatarate;double m_videocodecid;double m_audiocodecid;double m_audiosamplerate;double m_audiosamplesize;bool m_stereo;
};#endif // FLVMETADATA_H

(3)FlvPrase.cpp

#include <stdlib.h>
#include <string.h>
#include <assert.h>#include <iostream>
#include <fstream>#include "FlvParser.h"using namespace std;#define CheckBuffer(x) { if ((nBufSize-nOffset)<(x)) { nUsedLen = nOffset; return 0;} }int CFlvParser::CAudioTag::_aacProfile;
int CFlvParser::CAudioTag::_sampleRateIndex;
int CFlvParser::CAudioTag::_channelConfig;static const uint32_t nH264StartCode = 0x01000000;CFlvParser::CFlvParser()
{_pFlvHeader = NULL;_vjj = new CVideojj();
}CFlvParser::~CFlvParser()
{for (int i = 0; i < _vpTag.size(); i++){DestroyTag(_vpTag[i]);delete _vpTag[i];}if (_vjj != NULL)delete _vjj;
}int CFlvParser::Parse(uint8_t *pBuf, int nBufSize, int &nUsedLen)
{int nOffset = 0;if (_pFlvHeader == 0){CheckBuffer(9);_pFlvHeader = CreateFlvHeader(pBuf+nOffset);nOffset += _pFlvHeader->nHeadSize;}while (1){CheckBuffer(15); // nPrevSize(4字节) + Tag header(11字节)int nPrevSize = ShowU32(pBuf + nOffset);nOffset += 4;Tag *pTag = CreateTag(pBuf + nOffset, nBufSize-nOffset);if (pTag == NULL){nOffset -= 4;break;}nOffset += (11 + pTag->_header.nDataSize);_vpTag.push_back(pTag);}nUsedLen = nOffset;return 0;
}int CFlvParser::PrintInfo()
{Stat();cout << "vnum: " << _sStat.nVideoNum << " , anum: " << _sStat.nAudioNum << " , mnum: " << _sStat.nMetaNum << endl;cout << "maxTimeStamp: " << _sStat.nMaxTimeStamp << " ,nLengthSize: " << _sStat.nLengthSize << endl;cout << "Vjj SEI num: " << _vjj->_vVjjSEI.size() << endl;for (int i = 0; i < _vjj->_vVjjSEI.size(); i++)cout << "SEI time : " << _vjj->_vVjjSEI[i].nTimeStamp << endl;return 1;
}int CFlvParser::DumpH264(const std::string &path)
{fstream f;f.open(path.c_str(), ios_base::out|ios_base::binary);vector<Tag *>::iterator it_tag;for (it_tag = _vpTag.begin(); it_tag != _d(); it_tag++){if ((*it_tag)->_header.nType != 0x09)continue;f.write((char *)(*it_tag)->_pMedia, (*it_tag)->_nMediaLen);}f.close();return 1;
}int CFlvParser::DumpAAC(const std::string &path)
{fstream f;f.open(path.c_str(), ios_base::out | ios_base::binary);vector<Tag *>::iterator it_tag;for (it_tag = _vpTag.begin(); it_tag != _d(); it_tag++){if ((*it_tag)->_header.nType != 0x08)continue;CAudioTag *pAudioTag = (CAudioTag *)(*it_tag);if (pAudioTag->_nSoundFormat != 10)continue;if (pAudioTag->_nMediaLen!=0)f.write((char *)(*it_tag)->_pMedia, (*it_tag)->_nMediaLen);}f.close();return 1;
}int CFlvParser::DumpFlv(const std::string &path)
{fstream f;f.open(path.c_str(), ios_base::out | ios_base::binary);// write flv-headerf.write((char *)_pFlvHeader->pFlvHeader, _pFlvHeader->nHeadSize);uint32_t nLastTagSize = 0;// write flv-tagvector<Tag *>::iterator it_tag;for (it_tag = _vpTag.begin(); it_tag < _d(); it_tag++){uint32_t nn = WriteU32(nLastTagSize);f.write((char *)&nn, 4);//check duplicate start codeif ((*it_tag)->_header.nType == 0x09 && *((*it_tag)->_pTagData + 1) == 0x01) {bool duplicate = false;uint8_t *pStartCode = (*it_tag)->_pTagData + 5 + _nNalUnitLength;//printf("tagsize=%dn",(*it_tag)->_header.nDataSize);unsigned nalu_len = 0;uint8_t *p_nalu_len=(uint8_t *)&nalu_len;switch (_nNalUnitLength) {case 4:nalu_len = ShowU32((*it_tag)->_pTagData + 5);break;case 3:nalu_len = ShowU24((*it_tag)->_pTagData + 5);break;case 2:nalu_len = ShowU16((*it_tag)->_pTagData + 5);break;default:nalu_len = ShowU8((*it_tag)->_pTagData + 5);break;}/*printf("nalu_len=%un",nalu_len);printf("%x,%x,%x,%x,%x,%x,%x,%x,%xn",(*it_tag)->_pTagData[5],(*it_tag)->_pTagData[6],(*it_tag)->_pTagData[7],(*it_tag)->_pTagData[8],(*it_tag)->_pTagData[9],(*it_tag)->_pTagData[10],(*it_tag)->_pTagData[11],(*it_tag)->_pTagData[12],(*it_tag)->_pTagData[13]);*/uint8_t *pStartCodeRecord = pStartCode;int i;for (i = 0; i < (*it_tag)->_header.nDataSize - 5 - _nNalUnitLength - 4; ++i) {if (pStartCode[i] == 0x00 && pStartCode[i+1] == 0x00 && pStartCode[i+2] == 0x00 &&pStartCode[i+3] == 0x01) {if (pStartCode[i+4] == 0x67) {//printf("duplicate sps found!n");i += 4;continue;}else if (pStartCode[i+4] == 0x68) {//printf("duplicate pps found!n");i += 4;continue;}else if (pStartCode[i+4] == 0x06) {//printf("duplicate sei found!n");i += 4;continue;}else {i += 4;//printf("offset=%dn",i);duplicate = true;break;}}}if (duplicate) {nalu_len -= i;(*it_tag)->_header.nDataSize -= i;uint8_t *p = (uint8_t *)&((*it_tag)->_header.nDataSize);(*it_tag)->_pTagHeader[1] = p[2];(*it_tag)->_pTagHeader[2] = p[1];(*it_tag)->_pTagHeader[3] = p[0];//printf("after,tagsize=%dn",(int)ShowU24((*it_tag)->_pTagHeader + 1));//printf("%x,%x,%xn",(*it_tag)->_pTagHeader[1],(*it_tag)->_pTagHeader[2],(*it_tag)->_pTagHeader[3]);f.write((char *)(*it_tag)->_pTagHeader, 11);switch (_nNalUnitLength) {case 4:*((*it_tag)->_pTagData + 5) = p_nalu_len[3];*((*it_tag)->_pTagData + 6) = p_nalu_len[2];*((*it_tag)->_pTagData + 7) = p_nalu_len[1];*((*it_tag)->_pTagData + 8) = p_nalu_len[0];break;case 3:*((*it_tag)->_pTagData + 5) = p_nalu_len[2];*((*it_tag)->_pTagData + 6) = p_nalu_len[1];*((*it_tag)->_pTagData + 7) = p_nalu_len[0];break;case 2:*((*it_tag)->_pTagData + 5) = p_nalu_len[1];*((*it_tag)->_pTagData + 6) = p_nalu_len[0];break;default:*((*it_tag)->_pTagData + 5) = p_nalu_len[0];break;}//printf("after,nalu_len=%dn",(int)ShowU32((*it_tag)->_pTagData + 5));f.write((char *)(*it_tag)->_pTagData, pStartCode - (*it_tag)->_pTagData);/*printf("%x,%x,%x,%x,%x,%x,%x,%x,%xn",(*it_tag)->_pTagData[0],(*it_tag)->_pTagData[1],(*it_tag)->_pTagData[2],(*it_tag)->_pTagData[3],(*it_tag)->_pTagData[4],(*it_tag)->_pTagData[5],(*it_tag)->_pTagData[6],(*it_tag)->_pTagData[7],(*it_tag)->_pTagData[8]);*/f.write((char *)pStartCode + i, (*it_tag)->_header.nDataSize - (pStartCode - (*it_tag)->_pTagData));/*printf("write size:%dn", (pStartCode - (*it_tag)->_pTagData) +((*it_tag)->_header.nDataSize - (pStartCode - (*it_tag)->_pTagData)));*/} else {f.write((char *)(*it_tag)->_pTagHeader, 11);f.write((char *)(*it_tag)->_pTagData, (*it_tag)->_header.nDataSize);}} else {f.write((char *)(*it_tag)->_pTagHeader, 11);f.write((char *)(*it_tag)->_pTagData, (*it_tag)->_header.nDataSize);}nLastTagSize = 11 + (*it_tag)->_header.nDataSize;}uint32_t nn = WriteU32(nLastTagSize);f.write((char *)&nn, 4);f.close();return 1;
}int CFlvParser::Stat()
{for (int i = 0; i < _vpTag.size(); i++){switch (_vpTag[i]->_header.nType){case 0x08:_sStat.nAudioNum++;break;case 0x09:StatVideo(_vpTag[i]);break;case 0x12:_sStat.nMetaNum++;break;default:;}}return 1;
}int CFlvParser::StatVideo(Tag *pTag)
{_sStat.nVideoNum++;_sStat.nMaxTimeStamp = pTag->_header.nTimeStamp;if (pTag->_pTagData[0] == 0x17 && pTag->_pTagData[1] == 0x00){_sStat.nLengthSize = (pTag->_pTagData[9] & 0x03) + 1;}return 1;
}CFlvParser::FlvHeader *CFlvParser::CreateFlvHeader(uint8_t *pBuf)
{FlvHeader *pHeader = new FlvHeader;pHeader->nVersion = pBuf[3];        // 版本号pHeader->bHaveAudio = (pBuf[4] >> 2) & 0x01;    // 是否有音频pHeader->bHaveVideo = (pBuf[4] >> 0) & 0x01;    // 是否有视频pHeader->nHeadSize = ShowU32(pBuf + 5);         // 头部长度pHeader->pFlvHeader = new uint8_t[pHeader->nHeadSize];memcpy(pHeader->pFlvHeader, pBuf, pHeader->nHeadSize);return pHeader;
}int CFlvParser::DestroyFlvHeader(FlvHeader *pHeader)
{if (pHeader == NULL)return 0;delete pHeader->pFlvHeader;return 1;
}/*** @brief 复制header + body* @param pHeader* @param pBuf* @param nLeftLen*/
void CFlvParser::Tag::Init(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen)
{memcpy(&_header, pHeader, sizeof(TagHeader));// 复制标签头部信息 header_pTagHeader = new uint8_t[11];memcpy(_pTagHeader, pBuf, 11);      // 头部// 复制标签 body_pTagData = new uint8_t[_header.nDataSize];memcpy(_pTagData, pBuf + 11, _header.nDataSize);
}CFlvParser::CVideoTag::CVideoTag(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen, CFlvParser *pParser)
{// 初始化Init(pHeader, pBuf, nLeftLen);uint8_t *pd = _pTagData;_nFrameType = (pd[0] & 0xf0) >> 4;  // 帧类型_nCodecID = pd[0] & 0x0f;           // 视频编码类型// 开始解析if (_header.nType == 0x09 && _nCodecID == 7){ParseH264Tag(pParser);}
}/*** @brief  CAudioTag 音频Tag Data区域开始的第一个字节包含了音频数据的参数信息,* 从第二个字节开始为音频流数据,但第二个字节对于AAC也要判断是AAC sequence header还是AAC raw。* 第一个字节:SoundFormat 4bit 音频格式 0 = Linear PCM, platform endian1 =ADPCM; 2 = MP3; 3 = Linear PCM, little endian4 = Nellymoser 16-kHz mono ; 5 = Nellymoser 8-kHz mono6 = Nellymoser;  7 = G.711 A-law logarithmic PCM8 = G.711 mu-law logarithmic PCM; 9 = reserved10 = AAC ; 11  Speex 14 = MP3 8-Khz15 = Device-specific soundSoundRate 2bit 采样率 0 = 5.5-kHz; 1 = 11-kHz; 2 = 22-kHz; 3 = 44-kHz对于AAC总是3。但实际上AAC是可以支持到48khz以上的频率。SoundSize 1bit 采样精度  0 = snd8Bit; 1 = snd16Bit此参数仅适用于未压缩的格式,压缩后的格式都是将其设为1SoundType 1bit  0 = sndMono 单声道; 1 = sndStereo 立体声,双声道对于AAC总是1
If the SoundFormat indicates AAC, the SoundType should be set to 1 (stereo) and the
SoundRate should be set to 3 (44 kHz). However, this does not mean that AAC audio in FLV
is always stereo, 44 kHz data. Instead, the Flash Player ignores these values and
extracts the channel and sample rate data is encoded in the AAC bitstream.* @param pHeader* @param pBuf* @param nLeftLen* @param pParser*/
CFlvParser::CAudioTag::CAudioTag(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen, CFlvParser *pParser)
{Init(pHeader, pBuf, nLeftLen);uint8_t *pd = _pTagData;_nSoundFormat = (pd[0] & 0xf0) >> 4;    // 音频格式_nSoundRate = (pd[0] & 0x0c) >> 2;      // 采样率_nSoundSize = (pd[0] & 0x02) >> 1;      // 采样精度_nSoundType = (pd[0] & 0x01);           // 是否立体声if (_nSoundFormat == 10)                // AAC{ParseAACTag(pParser);}
}int CFlvParser::CAudioTag::ParseAACTag(CFlvParser *pParser)
{uint8_t *pd = _pTagData;// 数据包的类型:音频配置信息,音频数据int nAACPacketType = pd[1];// 如果是音频配置信息if (nAACPacketType == 0)    // AAC sequence header{// 解析配置信息ParseAudioSpecificConfig(pParser, pd); // 解析AudioSpecificConfig}// 如果是音频数据else if (nAACPacketType == 1)   // AAC RAW{// 解析音频数据ParseRawAAC(pParser, pd);}else{}return 1;
}int CFlvParser::CAudioTag::ParseAudioSpecificConfig(CFlvParser *pParser, uint8_t *pTagData)
{uint8_t *pd = _pTagData;_aacProfile = ((pd[2]&0xf8)>>3);    // 5bit AAC编码级别_sampleRateIndex = ((pd[2]&0x07)<<1) | (pd[3]>>7);  // 4bit 真正的采样率索引_channelConfig = (pd[3]>>3) & 0x0f;                 // 4bit 通道数量printf("----- AAC ------n");printf("profile:%dn", _aacProfile);printf("sample rate index:%dn", _sampleRateIndex);printf("channel config:%dn", _channelConfig);_pMedia = NULL;_nMediaLen = 0;return 1;
}int CFlvParser::CAudioTag::ParseRawAAC(CFlvParser *pParser, uint8_t *pTagData)
{uint64_t bits = 0;  // 占用8字节// 数据长度 跳过tag data的第一个第二字节int dataSize = _header.nDataSize - 2;   // 减去两字节的 audio tag data信息部分// 制作元数据WriteU64(bits, 12, 0xFFF);WriteU64(bits, 1, 0);WriteU64(bits, 2, 0);WriteU64(bits, 1, 1);WriteU64(bits, 2, _aacProfile - 1);WriteU64(bits, 4, _sampleRateIndex);WriteU64(bits, 1, 0);WriteU64(bits, 3, _channelConfig);WriteU64(bits, 1, 0);WriteU64(bits, 1, 0);WriteU64(bits, 1, 0);WriteU64(bits, 1, 0);WriteU64(bits, 13, 7 + dataSize);WriteU64(bits, 11, 0x7FF);WriteU64(bits, 2, 0);// WriteU64执行为上述的操作,最高的8bit还没有被移位到,实际是使用7个字节_nMediaLen = 7 + dataSize;_pMedia = new uint8_t[_nMediaLen];uint8_t p64[8];p64[0] = (uint8_t)(bits >> 56); // 是bits的最高8bit,实际为0p64[1] = (uint8_t)(bits >> 48); // 才是ADTS起始头 0xfff的高8bitp64[2] = (uint8_t)(bits >> 40);p64[3] = (uint8_t)(bits >> 32);p64[4] = (uint8_t)(bits >> 24);p64[5] = (uint8_t)(bits >> 16);p64[6] = (uint8_t)(bits >> 8);p64[7] = (uint8_t)(bits);memcpy(_pMedia, p64+1, 7);  // ADTS header, p64+1是从ADTS起始头开始memcpy(_pMedia + 7, pTagData + 2, dataSize); // AAC bodyreturn 1;
}CFlvParser::CMetaDataTag::CMetaDataTag(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen, CFlvParser *pParser)
{Init(pHeader, pBuf, nLeftLen);uint8_t *pd = _pTagData;m_amf1_type = ShowU8(pd+0);m_amf1_size = ShowU16(pd+1);if(m_amf1_type != 2){printf("no metadatan");return;}// 解析scriptif(strncmp((const char *)"onMetaData", (const char *)(pd + 3), 10) == 0)parseMeta(pParser);
}
double CFlvParser::CMetaDataTag::hexStr2double(const uint8_t* hex,const uint32_t length) {double ret = 0;char hexstr[length * 2];memset(hexstr, 0, sizeof(hexstr));for(uint32_t i = 0; i < length; i++){sprintf(hexstr + i * 2, "%02x", hex[i]);}sscanf(hexstr, "%llx", (unsigned long long*)&ret);return ret;
}
int CFlvParser::CMetaDataTag::parseMeta(CFlvParser *pParser)
{uint8_t *pd = _pTagData;int dataSize = _header.nDataSize;uint32_t arrayLen = 0;uint32_t offset = 13; // Type + Value_Size + Value占用13字节uint32_t nameLen = 0;double doubleValue = 0;string strValue = "";bool boolValue = false;uint32_t valueLen = 0;uint8_t u8Value = 0;if(pd[offset++] == 0x08)    // 0x8 onMetaData{arrayLen = ShowU32(pd + offset);offset += 4;    //跳过 [ECMAArrayLength]占用的字节printf("ArrayLen = %dn", arrayLen);}else{printf("metadata format error!!!");return -1;}for(uint32_t i = 0; i < arrayLen; i++){doubleValue = 0;boolValue = false;strValue = "";// 读取字段长度nameLen = ShowU16(pd + offset);offset += 2;            // 跳过2字节字段长度char name[nameLen + 1]; // 获取字段名称memset(name, 0, sizeof(name));memcpy(name, &pd[offset], nameLen);name[nameLen + 1] = '';offset += nameLen;      // 跳过字段名占用的长度uint8_t amfType = pd[offset++];switch(amfType)    // 判别值的类型{case 0x0: //Number type, 就是double类型, 占用8字节doubleValue = hexStr2double(&pd[offset], 8);offset += 8;        // 跳过8字节break;case 0x1: //Boolean type, 占用1字节u8Value = ShowU8(pd+offset);offset += 1;        // 跳过1字节if(u8Value != 0x00)boolValue = true;elseboolValue = false;break;case 0x2: //String typevalueLen = ShowU16(pd + offset);offset += 2;    // 跳过2字节 lengthstrValue.append(pd + offset, pd + offset + valueLen);strValue.append("");offset += valueLen; // 跳过字段的值的长度break;default:printf("un handle amfType:%dn", amfType);break;}if(strncmp(name, "duration", 8)	== 0){m_duration = doubleValue;}else if(strncmp(name, "width", 5)	== 0){m_width = doubleValue;}else if(strncmp(name, "height", 6) == 0){m_height = doubleValue;}else if(strncmp(name, "videodatarate", 13) == 0){m_videodatarate = doubleValue;}else if(strncmp(name, "framerate", 9) == 0){m_framerate = doubleValue;}else if(strncmp(name, "videocodecid", 12) == 0){m_videocodecid = doubleValue;}else if(strncmp(name, "audiodatarate", 13) == 0){m_audiodatarate = doubleValue;}else if(strncmp(name, "audiosamplerate", 15) == 0){m_audiosamplerate = doubleValue;}else if(strncmp(name, "audiosamplesize", 15) == 0){m_audiosamplesize = doubleValue;}else if(strncmp(name, "stereo", 6) == 0){m_stereo = boolValue;}else if(strncmp(name, "audiocodecid", 12) == 0){m_audiocodecid = doubleValue;}else if(strncmp(name, "major_brand", 11) == 0){m_major_brand = strValue;}else if(strncmp(name, "minor_version", 13) == 0){m_minor_version = strValue;}else if(strncmp(name, "compatible_brands", 17) == 0){m_compatible_brands = strValue;}else if(strncmp(name, "encoder", 7) == 0){m_encoder = strValue;}else if(strncmp(name, "filesize", 8) == 0){m_filesize = doubleValue;}}printMeta();return 1;
}void CFlvParser::CMetaDataTag::printMeta()
{printf("nduration: %0.2lfs, filesize: %.0lfbytesn", m_duration, m_filesize);printf("width: %0.0lf, height: %0.0lfn", m_width, m_height);printf("videodatarate: %0.2lfkbps, framerate: %0.0lffpsn", m_videodatarate, m_framerate);printf("videocodecid: %0.0lfn", m_videocodecid);printf("audiodatarate: %0.2lfkbps, audiosamplerate: %0.0lfKhzn",m_audiodatarate, m_audiosamplerate);printf("audiosamplesize: %0.0lfbit, stereo: %dn", m_audiosamplesize, m_stereo);printf("audiocodecid: %0.0lfn", m_audiocodecid);printf("major_brand: %s, minor_version: %sn", m_major_brand.c_str(), m_minor_version.c_str());printf("compatible_brands: %s, encoder: %snn", m_compatible_brands.c_str(), m_encoder.c_str());
}CFlvParser::Tag *CFlvParser::CreateTag(uint8_t *pBuf, int nLeftLen)
{// 开始解析标签头部TagHeader header;header.nType = ShowU8(pBuf+0);  // 类型header.nDataSize = ShowU24(pBuf + 1);   // 标签body的长度header.nTimeStamp = ShowU24(pBuf + 4);  // 时间戳 低24bitheader.nTSEx = ShowU8(pBuf + 7);        // 时间戳的扩展字段, 高8bitheader.nStreamID = ShowU24(pBuf + 8);   // 流的idheader.nTotalTS = (uint32_t)((header.nTSEx << 24)) + header.nTimeStamp;// 标签头部解析结束//    cout << "total TS : " << header.nTotalTS << endl;
//    cout << "nLeftLen : " << nLeftLen << " , nDataSize : " << header.nDataSize << endl;if ((header.nDataSize + 11) > nLeftLen){return NULL;}Tag *pTag;switch (header.nType) {case 0x09:  // 视频类型的TagpTag = new CVideoTag(&header, pBuf, nLeftLen, this);break;case 0x08:  // 音频类型的TagpTag = new CAudioTag(&header, pBuf, nLeftLen, this);break;case 0x12:  // script TagpTag = new CMetaDataTag(&header, pBuf, nLeftLen, this);break;default:    // script类型的TagpTag = new Tag();pTag->Init(&header, pBuf, nLeftLen);}return pTag;
}int CFlvParser::DestroyTag(Tag *pTag)
{if (pTag->_pMedia != NULL)delete []pTag->_pMedia;if (pTag->_pTagData!=NULL)delete []pTag->_pTagData;if (pTag->_pTagHeader != NULL)delete []pTag->_pTagHeader;return 1;
}int CFlvParser::CVideoTag::ParseH264Tag(CFlvParser *pParser)
{uint8_t *pd = _pTagData;/*** 数据包的类型** 视频数据被压缩之后被打包成数据包在网上传输** 有两种类型的数据包:视频信息包(sps、pps等)和视频数据包(视频的压缩数据)*/int nAVCPacketType = pd[1];int nCompositionTime = CFlvParser::ShowU24(pd + 2);// 如果是视频配置信息if (nAVCPacketType == 0)    // AVC sequence header{ParseH264Configuration(pParser, pd);}// 如果是视频数据else if (nAVCPacketType == 1) // AVC NALU{ParseNalu(pParser, pd);}else{}return 1;
}
/*** @brief
AVCDecoderConfigurationRecord {uint32_t(8) configurationVersion = 1;  [0]uint32_t(8) AVCProfileIndication;       [1]uint32_t(8) profile_compatibility;      [2]uint32_t(8) AVCLevelIndication;         [3]bit(6) reserved = ‘111111’b;            [4]uint32_t(2) lengthSizeMinusOne;         [4] 计算方法是 1 + (lengthSizeMinusOne & 3),实际计算结果一直是4bit(3) reserved = ‘111’b;                   [5]uint32_t(5) numOfSequenceParameterSets; [5] SPS 的个数,计算方法是 numOfSequenceParameterSets & 0x1F,实际计算结果一直为1for (i=0; i< numOfSequenceParameterSets; i++) {uint32_t(16) sequenceParameterSetLength ;   [6,7]bit(8*sequenceParameterSetLength) sequenceParameterSetNALUnit;}uint32_t(8) numOfPictureParameterSets;      PPS 的个数,一直为1for (i=0; i< numOfPictureParameterSets; i++) {uint32_t(16) pictureParameterSetLength;bit(8*pictureParameterSetLength) pictureParameterSetNALUnit;}
}_nNalUnitLength 这个变量告诉我们用几个字节来存储NALU的长度,如果NALULengthSizeMinusOne是0,
那么每个NALU使用一个字节的前缀来指定长度,那么每个NALU包的最大长度是255字节,
这个明显太小了,使用2个字节的前缀来指定长度,那么每个NALU包的最大长度是64K字节,
也不一定够,一般分辨率达到1280*720 的图像编码出的I帧,可能大于64K;3字节是比较完美的,
但是因为一些原因(例如对齐)没有被广泛支持;因此4字节长度的前缀是目前使用最多的方式* @param pParser* @param pTagData* @return*/
int CFlvParser::CVideoTag::ParseH264Configuration(CFlvParser *pParser, uint8_t *pTagData)
{uint8_t *pd = pTagData;// 跨过 Tag Data的VIDEODATA(1字节) AVCVIDEOPACKET(AVCPacketType(1字节) 和CompositionTime(3字节) 4字节)// 总共跨过5个字节// NalUnit长度表示占用的字节pParser->_nNalUnitLength = (pd[9] & 0x03) + 1;  // lengthSizeMinusOne 9 = 5 + 4int sps_size, pps_size;// sps(序列参数集)的长度sps_size = CFlvParser::ShowU16(pd + 11);        // sequenceParameterSetLength 11 = 5 + 6// pps(图像参数集)的长度pps_size = CFlvParser::ShowU16(pd + 11 + (2 + sps_size) + 1);   // pictureParameterSetLength// 元数据的长度_nMediaLen = 4 + sps_size + 4 + pps_size;   // 添加start code_pMedia = new uint8_t[_nMediaLen];// 保存元数据memcpy(_pMedia, &nH264StartCode, 4);memcpy(_pMedia + 4, pd + 11 + 2, sps_size);memcpy(_pMedia + 4 + sps_size, &nH264StartCode, 4);memcpy(_pMedia + 4 + sps_size + 4, pd + 11 + 2 + sps_size + 2 + 1, pps_size);return 1;
}int CFlvParser::CVideoTag::ParseNalu(CFlvParser *pParser, uint8_t *pTagData)
{uint8_t *pd = pTagData;int nOffset = 0;_pMedia = new uint8_t[_header.nDataSize+10];_nMediaLen = 0;// 跨过 Tag Data的VIDEODATA(1字节) AVCVIDEOPACKET(AVCPacketType和CompositionTime 4字节)nOffset = 5; // 总共跨过5个字节 132 - 5 = 127 = _nNalUnitLength(4字节)  + NALU(123字节)//                                           startcode(4字节)  + NALU(123字节) = 127while (1){// 如果解析完了一个Tag,那么就跳出循环if (nOffset >= _header.nDataSize)break;// 计算NALU(视频数据被包装成NALU在网上传输)的长度,// 一个tag可能包含多个nalu, 所以每个nalu前面有NalUnitLength字节表示每个nalu的长度int nNaluLen;switch (pParser->_nNalUnitLength){case 4:nNaluLen = CFlvParser::ShowU32(pd + nOffset);break;case 3:nNaluLen = CFlvParser::ShowU24(pd + nOffset);break;case 2:nNaluLen = CFlvParser::ShowU16(pd + nOffset);break;default:nNaluLen = CFlvParser::ShowU8(pd + nOffset);}// 获取NALU的起始码memcpy(_pMedia + _nMediaLen, &nH264StartCode, 4);// 复制NALU的数据memcpy(_pMedia + _nMediaLen + 4, pd + nOffset + pParser->_nNalUnitLength, nNaluLen);// 解析NALU
//        pParser->_vjj->Process(_pMedia+_nMediaLen, 4+nNaluLen, _header.nTotalTS);_nMediaLen += (4 + nNaluLen);nOffset += (pParser->_nNalUnitLength + nNaluLen);}return 1;
}

(4) FlvParse.h

#ifndef FLVPARSER_H
#define FLVPARSER_H#include <iostream>
#include <vector>
#include <stdint.h>
#include "Videojj.h"using namespace std;typedef unsigned long long uint64_t;class CFlvParser
{
public:CFlvParser();virtual ~CFlvParser();int Parse(uint8_t *pBuf, int nBufSize, int &nUsedLen);int PrintInfo();int DumpH264(const std::string &path);int DumpAAC(const std::string &path);int DumpFlv(const std::string &path);private:// FLV头typedef struct FlvHeader_s{int nVersion; // 版本int bHaveVideo; // 是否包含视频int bHaveAudio; // 是否包含音频int nHeadSize;  // FLV头部长度/*** 指向存放FLV头部的buffer** 上面的三个成员指明了FLV头部的信息,是从FLV的头部中“翻译”得到的,** 真实的FLV头部是一个二进制比特串,放在一个buffer中,由pFlvHeader成员指明*/uint8_t *pFlvHeader;} FlvHeader;// Tag头部struct TagHeader{int nType;      // 类型int nDataSize;  // 标签body的大小int nTimeStamp; // 时间戳int nTSEx;      // 时间戳的扩展字节int nStreamID;  // 流的ID,总是0uint32_t nTotalTS;  // 完整的时间戳nTimeStamp和nTSEx拼装TagHeader() : nType(0), nDataSize(0), nTimeStamp(0), nTSEx(0), nStreamID(0), nTotalTS(0) {}~TagHeader() {}};class Tag{public:Tag() : _pTagHeader(NULL), _pTagData(NULL), _pMedia(NULL), _nMediaLen(0) {}void Init(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen);TagHeader _header;uint8_t *_pTagHeader;   // 指向标签头部uint8_t *_pTagData;     // 指向标签body,原始的tag data数据uint8_t *_pMedia;       // 指向标签的元数据,改造后的数据int _nMediaLen;         // 数据长度};class CVideoTag : public Tag{public:/*** @brief CVideoTag* @param pHeader* @param pBuf 整个tag的起始地址* @param nLeftLen* @param pParser*/CVideoTag(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen, CFlvParser *pParser);int _nFrameType;    // 帧类型int _nCodecID;      // 视频编解码类型int ParseH264Tag(CFlvParser *pParser);int ParseH264Configuration(CFlvParser *pParser, uint8_t *pTagData);int ParseNalu(CFlvParser *pParser, uint8_t *pTagData);};class CAudioTag : public Tag{public:CAudioTag(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen, CFlvParser *pParser);int _nSoundFormat;  // 音频编码类型int _nSoundRate;    // 采样率int _nSoundSize;    // 精度int _nSoundType;    // 类型// aacstatic int _aacProfile;     // 对应AAC profilestatic int _sampleRateIndex;    // 采样率索引static int _channelConfig;      // 通道设置int ParseAACTag(CFlvParser *pParser);int ParseAudioSpecificConfig(CFlvParser *pParser, uint8_t *pTagData);int ParseRawAAC(CFlvParser *pParser, uint8_t *pTagData);};class CMetaDataTag : public Tag{public:CMetaDataTag(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen, CFlvParser *pParser);double hexStr2double(const unsigned char* hex, const unsigned int length);int parseMeta(CFlvParser *pParser);void printMeta();uint8_t m_amf1_type;uint32_t m_amf1_size;uint8_t m_amf2_type;unsigned char *m_meta;unsigned int m_length;double m_duration;double m_width;double m_height;double m_videodatarate;double m_framerate;double m_videocodecid;double m_audiodatarate;double m_audiosamplerate;double m_audiosamplesize;bool m_stereo;double m_audiocodecid;string m_major_brand;string m_minor_version;string m_compatible_brands;string m_encoder;double m_filesize;};struct FlvStat{int nMetaNum, nVideoNum, nAudioNum;int nMaxTimeStamp;int nLengthSize;FlvStat() : nMetaNum(0), nVideoNum(0), nAudioNum(0), nMaxTimeStamp(0), nLengthSize(0){}~FlvStat() {}};static uint32_t ShowU32(uint8_t *pBuf) { return (pBuf[0] << 24) | (pBuf[1] << 16) | (pBuf[2] << 8) | pBuf[3]; }static uint32_t ShowU24(uint8_t *pBuf) { return (pBuf[0] << 16) | (pBuf[1] << 8) | (pBuf[2]); }static uint32_t ShowU16(uint8_t *pBuf) { return (pBuf[0] << 8) | (pBuf[1]); }static uint32_t ShowU8(uint8_t *pBuf) { return (pBuf[0]); }static void WriteU64(uint64_t & x, int length, int value){uint64_t mask = 0xFFFFFFFFFFFFFFFF >> (64 - length);x = (x << length) | ((uint64_t)value & mask);}static uint32_t WriteU32(uint32_t n){uint32_t nn = 0;uint8_t *p = (uint8_t *)&n;uint8_t *pp = (uint8_t *)&nn;pp[0] = p[3];pp[1] = p[2];pp[2] = p[1];pp[3] = p[0];return nn;}friend class Tag;private:FlvHeader *CreateFlvHeader(uint8_t *pBuf);int DestroyFlvHeader(FlvHeader *pHeader);Tag *CreateTag(uint8_t *pBuf, int nLeftLen);int DestroyTag(Tag *pTag);int Stat();int StatVideo(Tag *pTag);int IsUserDataTag(Tag *pTag);private:FlvHeader* _pFlvHeader;vector<Tag *> _vpTag;FlvStat _sStat;CVideojj *_vjj;// H.264int _nNalUnitLength;};#endif // FLVPARSER_H

(5)main()

#include <stdlib.h>
#include <string.h>#include <iostream>
#include <fstream>#include "FlvParser.h"using namespace std;void Process(fstream &fin, const char *filename);int main(int argc, char *argv[])
{cout << "Hi, this is FLV parser test program!n";if (argc != 3){cout << &# [input flv] [output flv]" << endl;return 0;}fstream fin;fin.open(argv[1], ios_base::in | ios_base::binary);if (!fin)return 0;Process(fin, argv[2]);fin.close();return 1;
}void Process(fstream &fin, const char *filename)
{CFlvParser parser;int nBufSize = 2*1024 * 1024;int nFlvPos = 0;uint8_t *pBuf, *pBak;pBuf = new uint8_t[nBufSize];pBak = new uint8_t[nBufSize];while (1){int nReadNum = 0;int nUsedLen = ad((char *)pBuf + nFlvPos, nBufSize - nFlvPos);nReadNum = unt();if (nReadNum == 0)break;nFlvPos += nReadNum;parser.Parse(pBuf, nFlvPos, nUsedLen);if (nFlvPos != nUsedLen){memcpy(pBak, pBuf + nUsedLen, nFlvPos - nUsedLen);memcpy(pBuf, pBak, nFlvPos - nUsedLen);}nFlvPos -= nUsedLen;}parser.PrintInfo();parser.DumpH264("parser.264");parser.DumpAAC("parser.aac");//dump into flvparser.DumpFlv(filename);delete []pBak;delete []pBuf;
}

(6) vadbg.cpp

#include <fstream>#include "vadbg.h"using namespace std;namespace vadbg
{void DumpString(std::string path, std::string str){ofstream fin;fin.open(path.c_str(), ios_base::out);fin << str.c_str();fin.close();}void DumpBuffer(std::string path, uint8_t *pBuffer, int nBufSize){ofstream fin;fin.open(path.c_str(), ios_base::out | ios_base::binary);fin.write((char *)pBuffer, nBufSize);fin.close();}
}

(7)vadbg.h

#ifndef VADBG_H
#define VADBG_H#include <iostream>namespace vadbg
{void DumpString(std::string path, std::string str);void DumpBuffer(std::string path, uint8_t *pBuffer, int nBufSize);
}#endif // VADBG_H

(8)Videojj.cpp

#include <stdlib.h>
#include <string.h>#include "vadbg.h"
#include "Videojj.h"CVideojj::CVideojj()
{}CVideojj::~CVideojj()
{int i;for (i = 0; i < _vVjjSEI.size(); i++){delete _vVjjSEI[i].szUD;}
}// 用户可以根据自己的需要,对该函数进行修改或者扩展
// 下面这个函数的功能大致就是往视频中写入SEI信息
int CVideojj::Process(uint8_t *pNalu, int nNaluLen, int nTimeStamp)
{// 如果起始码后面的两个字节是0x05或者0x06,那么表示IDR图像或者SEI信息if (pNalu[4] != 0x06 || pNalu[5] != 0x05)return 0;uint8_t *p = pNalu + 4 + 2;while (*p++ == 0xff);const char *szVideojjUUID = "VideojjLeonUUID";char *pp = (char *)p;for (int i = 0; i < strlen(szVideojjUUID); i++){if (pp[i] != szVideojjUUID[i])return 0;}VjjSEI sei;sei.nTimeStamp = nTimeStamp;sei.nLen = nNaluLen - (pp - (char *)pNalu) - 16 - 1;sei.szUD = new char[sei.nLen];memcpy(sei.szUD, pp + 16, sei.nLen);_vVjjSEI.push_back(sei);return 1;
}

(9)Videojj.h

#ifndef VIDEOJJ_H
#define VIDEOJJ_H#include <vector>
#include <cstdint>
class CFlvParser;typedef struct VjjSEI_s
{char *szUD;int nLen;int nTimeStamp; // ms
} VjjSEI;class CVideojj
{
public:CVideojj();virtual ~CVideojj();int Process(uint8_t *pNalu, int nNaluLen, int nTimeStamp);private:friend class CFlvParser;std::vector<VjjSEI> _vVjjSEI;
};#endif // VIDEOJJ_H

五、音频解码

/**
* @projectName   07-05-decode_audio
* @brief         解码音频,主要的测试格式aac和mp3
* @author        Liao Qingfu
* @date          2020-01-16
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <libavutil/frame.h>
#include <libavutil/mem.h>#include <libavcodec/avcodec.h>#define AUDIO_INBUF_SIZE 20480
#define AUDIO_REFILL_THRESH 4096static char err_buf[128] = {0};
static char* av_get_err(int errnum)
{av_strerror(errnum, err_buf, 128);return err_buf;
}static void print_sample_format(const AVFrame *frame)
{printf("ar-samplerate: %uHzn", frame->sample_rate);printf("ac-channel: %un", frame->channels);printf("f-format: %un", frame->format);// 格式需要注意,实际存储到本地文件时已经改成交错模式
}static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,FILE *outfile)
{int i, ch;int ret, data_size;/* send the packet with the compressed data to the decoder */ret = avcodec_send_packet(dec_ctx, pkt);if(ret == AVERROR(EAGAIN)){fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.n");}else if (ret < 0){fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%dn",av_get_err(ret), pkt->size);
//        exit(1);return;}/* read all the output frames (infile general there may be any number of them */while (ret >= 0){// 对于frame, avcodec_receive_frame内部每次都先调用ret = avcodec_receive_frame(dec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)return;else if (ret < 0){fprintf(stderr, "Error during decodingn");exit(1);}data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);if (data_size < 0){/* This should not occur, checking just for paranoia */fprintf(stderr, "Failed to calculate data sizen");exit(1);}static int s_print_format = 0;if(s_print_format == 0){s_print_format = 1;print_sample_format(frame);}/**P表示Planar(平面),其数据格式排列方式为 :(每个LLLLLLRRRRRR为一个音频帧)而不带P的数据格式(即交错排列)排列方式为&#(每个LR为一个音频样本)播放范例:   ffplay -ar 48000 -ac 2 -f f32le believe.pcm*/for (i = 0; i < frame->nb_samples; i++){for (ch = 0; ch < dec_ctx->channels; ch++)  // 交错的方式写入, 大部分float的格式输出fwrite(frame->data[ch] + data_size*i, 1, data_size, outfile);}}
}
// 播放范例:   ffplay -ar 48000 -ac 2 -f f32le believe.pcm
int main(int argc, char **argv)
{const char *outfilename;const char *filename;const AVCodec *codec;AVCodecContext *codec_ctx= NULL;AVCodecParserContext *parser = NULL;int len = 0;int ret = 0;FILE *infile = NULL;FILE *outfile = NULL;uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];uint8_t *data = NULL;size_t   data_size = 0;AVPacket *pkt = NULL;AVFrame *decoded_frame = NULL;if (argc <= 2){fprintf(stderr, "Usage: %s <input file> <output file>n", argv[0]);exit(0);}filename    = argv[1];outfilename = argv[2];pkt = av_packet_alloc();enum AVCodecID audio_codec_id = AV_CODEC_ID_AAC;if(strstr(filename, "aac") != NULL){audio_codec_id = AV_CODEC_ID_AAC;}else if(strstr(filename, "mp3") != NULL){audio_codec_id = AV_CODEC_ID_MP3;}else{printf("default codec id:%dn", audio_codec_id);}// 查找解码器codec = avcodec_find_decoder(audio_codec_id);  // AV_CODEC_ID_AACif (!codec) {fprintf(stderr, "Codec not foundn");exit(1);}// 获取裸流的解析器 AVCodecParserContext(数据)  +  AVCodecParser(方法)parser = av_parser_init(codec->id);if (!parser) {fprintf(stderr, "Parser not foundn");exit(1);}// 分配codec上下文codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {fprintf(stderr, "Could not allocate audio codec contextn");exit(1);}// 将解码器和解码器上下文进行关联if (avcodec_open2(codec_ctx, codec, NULL) < 0) {fprintf(stderr, "Could not open codecn");exit(1);}// 打开输入文件infile = fopen(filename, "rb");if (!infile) {fprintf(stderr, "Could not open %sn", filename);exit(1);}// 打开输出文件outfile = fopen(outfilename, "wb");if (!outfile) {av_free(codec_ctx);exit(1);}// 读取文件进行解码data      = inbuf;data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, infile);while (data_size > 0){if (!decoded_frame){if (!(decoded_frame = av_frame_alloc())){fprintf(stderr, "Could not allocate audio framen");exit(1);}}ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,data, data_size,AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);if (ret < 0){fprintf(stderr, "Error while parsingn");exit(1);}data      += ret;   // 跳过已经解析的数据data_size -= ret;   // 对应的缓存大小也做相应减小if (pkt->size)decode(codec_ctx, pkt, decoded_frame, outfile);if (data_size < AUDIO_REFILL_THRESH)    // 如果数据少了则再次读取{memmove(inbuf, data, data_size);    // 把之前剩的数据拷贝到buffer的起始位置data = inbuf;// 读取数据 长度: AUDIO_INBUF_SIZE - data_sizelen = fread(data + data_size, 1, AUDIO_INBUF_SIZE - data_size, infile);if (len > 0)data_size += len;}}/* 冲刷解码器 */pkt->data = NULL;   // 让其进入drain modepkt->size = 0;decode(codec_ctx, pkt, decoded_frame, outfile);fclose(outfile);fclose(infile);avcodec_free_context(&codec_ctx);av_parser_close(parser);av_frame_free(&decoded_frame);av_packet_free(&pkt);printf("main finish, please enter Enter and exitn");return 0;
}

六、视频解码

/**
* @projectName   07-05-decode_audio
* @brief         解码音频,主要的测试格式aac和mp3
* @author        Liao Qingfu
* @date          2020-01-16
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <libavutil/frame.h>
#include <libavutil/mem.h>#include <libavcodec/avcodec.h>#define VIDEO_INBUF_SIZE 20480
#define VIDEO_REFILL_THRESH 4096static char err_buf[128] = {0};
static char* av_get_err(int errnum)
{av_strerror(errnum, err_buf, 128);return err_buf;
}static void print_video_format(const AVFrame *frame)
{printf("width: %un", frame->width);printf("height: %un", frame->height);printf("format: %un", frame->format);// 格式需要注意
}static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,FILE *outfile)
{int ret;/* send the packet with the compressed data to the decoder */ret = avcodec_send_packet(dec_ctx, pkt);if(ret == AVERROR(EAGAIN)){fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.n");}else if (ret < 0){fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%dn",av_get_err(ret), pkt->size);return;}/* read all the output frames (infile general there may be any number of them */while (ret >= 0){// 对于frame, avcodec_receive_frame内部每次都先调用ret = avcodec_receive_frame(dec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)return;else if (ret < 0){fprintf(stderr, "Error during decodingn");exit(1);}static int s_print_format = 0;if(s_print_format == 0){s_print_format = 1;print_video_format(frame);}// 一般H264默认为 AV_PIX_FMT_YUV420P, 具体怎么强制转为 AV_PIX_FMT_YUV420P 在音视频合成输出的时候讲解// frame->linesize[1]  对齐的问题// 正确写法  linesize[]代表每行的字节数量,所以每行的偏移是linesize[]for(int j=0; j<frame->height; j++)fwrite(frame->data[0] + j * frame->linesize[0], 1, frame->width, outfile);for(int j=0; j<frame->height/2; j++)fwrite(frame->data[1] + j * frame->linesize[1], 1, frame->width/2, outfile);for(int j=0; j<frame->height/2; j++)fwrite(frame->data[2] + j * frame->linesize[2], 1, frame->width/2, outfile);// 错误写法 用source.200kbps.766x322_10s.h264测试时可以看出该种方法是错误的//  写入y分量
//        fwrite(frame->data[0], 1, frame->width * frame->height,  outfile);//Y
//        // 写入u分量
//        fwrite(frame->data[1], 1, (frame->width) *(frame->height)/4,outfile);//U:宽高均是Y的一半
//        //  写入v分量
//        fwrite(frame->data[2], 1, (frame->width) *(frame->height)/4,outfile);//V:宽高均是Y的一半}
}
// 注册测试的时候不同分辨率的问题
// 提取H264: ffmpeg -i source.200kbps.768x320_10s.flv -vcodec libx264 -an -f h264 source.200kbps.768x320_10s.h264
// 提取MPEG2: ffmpeg -i source.200kbps.768x320_10s.flv -vcodec mpeg2video -an -f mpeg2video source.200kbps.768x320_10s.mpeg2
// 播放:ffplay -pixel_format yuv420p -video_size 768x320 -framerate 25  source.200kbps.768x320_10s.yuv
int main(int argc, char **argv)
{const char *outfilename;const char *filename;const AVCodec *codec;AVCodecContext *codec_ctx= NULL;AVCodecParserContext *parser = NULL;int len = 0;int ret = 0;FILE *infile = NULL;FILE *outfile = NULL;// AV_INPUT_BUFFER_PADDING_SIZE 在输入比特流结尾的要求附加分配字节的数量上进行解码uint8_t inbuf[VIDEO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];uint8_t *data = NULL;size_t   data_size = 0;AVPacket *pkt = NULL;AVFrame *decoded_frame = NULL;if (argc <= 2){fprintf(stderr, "Usage: %s <input file> <output file>n", argv[0]);exit(0);}filename    = argv[1];outfilename = argv[2];pkt = av_packet_alloc();enum AVCodecID video_codec_id = AV_CODEC_ID_H264;if(strstr(filename, "264") != NULL){video_codec_id = AV_CODEC_ID_H264;}else if(strstr(filename, "mpeg2") != NULL){video_codec_id = AV_CODEC_ID_MPEG2VIDEO;}else{printf("default codec id:%dn", video_codec_id);}// 查找解码器codec = avcodec_find_decoder(video_codec_id);  // AV_CODEC_ID_H264if (!codec) {fprintf(stderr, "Codec not foundn");exit(1);}// 获取裸流的解析器 AVCodecParserContext(数据)  +  AVCodecParser(方法)parser = av_parser_init(codec->id);if (!parser) {fprintf(stderr, "Parser not foundn");exit(1);}// 分配codec上下文codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {fprintf(stderr, "Could not allocate audio codec contextn");exit(1);}// 将解码器和解码器上下文进行关联if (avcodec_open2(codec_ctx, codec, NULL) < 0) {fprintf(stderr, "Could not open codecn");exit(1);}// 打开输入文件infile = fopen(filename, "rb");if (!infile) {fprintf(stderr, "Could not open %sn", filename);exit(1);}// 打开输出文件outfile = fopen(outfilename, "wb");if (!outfile) {av_free(codec_ctx);exit(1);}// 读取文件进行解码data      = inbuf;data_size = fread(inbuf, 1, VIDEO_INBUF_SIZE, infile);while (data_size > 0){if (!decoded_frame){if (!(decoded_frame = av_frame_alloc())){fprintf(stderr, "Could not allocate audio framen");exit(1);}}ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,data, data_size,AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);if (ret < 0){fprintf(stderr, "Error while parsingn");exit(1);}data      += ret;   // 跳过已经解析的数据data_size -= ret;   // 对应的缓存大小也做相应减小if (pkt->size)decode(codec_ctx, pkt, decoded_frame, outfile);if (data_size < VIDEO_REFILL_THRESH)    // 如果数据少了则再次读取{memmove(inbuf, data, data_size);    // 把之前剩的数据拷贝到buffer的起始位置data = inbuf;// 读取数据 长度: VIDEO_INBUF_SIZE - data_sizelen = fread(data + data_size, 1, VIDEO_INBUF_SIZE - data_size, infile);if (len > 0)data_size += len;}}/* 冲刷解码器 */pkt->data = NULL;   // 让其进入drain modepkt->size = 0;decode(codec_ctx, pkt, decoded_frame, outfile);fclose(outfile);fclose(infile);avcodec_free_context(&codec_ctx);av_parser_close(parser);av_frame_free(&decoded_frame);av_packet_free(&pkt);printf("main finish, please enter Enter and exitn");return 0;
}

七、Mp4解封装

#include <stdio.h>#include "libavutil/log.h"
#include "libavformat/avformat.h"#define ERROR_STRING_SIZE 1024#define ADTS_HEADER_LEN  7;const int sampling_frequencies[] = {96000,  // 0x088200,  // 0x164000,  // 0x248000,  // 0x344100,  // 0x432000,  // 0x524000,  // 0x622050,  // 0x716000,  // 0x812000,  // 0x911025,  // 0xa8000   // 0xb// 0xc d e f是保留的
};int adts_header(char * const p_adts_header, const int data_length,const int profile, const int samplerate,const int channels)
{int sampling_frequency_index = 3; // 默认使用48000hzint adtsLen = data_length + 7;int frequencies_size = sizeof(sampling_frequencies) / sizeof(sampling_frequencies[0]);int i = 0;for(i = 0; i < frequencies_size; i++){if(sampling_frequencies[i] == samplerate){sampling_frequency_index = i;break;}}if(i >= frequencies_size){printf("unsupport samplerate:%dn", samplerate);return -1;}p_adts_header[0] = 0xff;         //syncword:0xfff                          高8bitsp_adts_header[1] = 0xf0;         //syncword:0xfff                          低4bitsp_adts_header[1] |= (0 << 3);    //MPEG Version:0 for MPEG-4,1 for MPEG-2  1bitp_adts_header[1] |= (0 << 1);    //Layer:0                                 2bitsp_adts_header[1] |= 1;           //protection absent:1                     1bitp_adts_header[2] = (profile)<<6;            //profile:profile               2bitsp_adts_header[2] |= (sampling_frequency_index & 0x0f)<<2; //sampling frequency index:sampling_frequency_index  4bitsp_adts_header[2] |= (0 << 1);             //private bit:0                   1bitp_adts_header[2] |= (channels & 0x04)>>2; //channel configuration:channels  高1bitp_adts_header[3] = (channels & 0x03)<<6; //channel configuration:channels 低2bitsp_adts_header[3] |= (0 << 5);               //original:0                1bitp_adts_header[3] |= (0 << 4);               //home:0                    1bitp_adts_header[3] |= (0 << 3);               //copyright id bit:0        1bitp_adts_header[3] |= (0 << 2);               //copyright id start:0      1bitp_adts_header[3] |= ((adtsLen & 0x1800) >> 11);           //frame length:value   高2bitsp_adts_header[4] = (uint8_t)((adtsLen & 0x7f8) >> 3);     //frame length:value    中间8bitsp_adts_header[5] = (uint8_t)((adtsLen & 0x7) << 5);       //frame length:value    低3bitsp_adts_header[5] |= 0x1f;                                 //buffer fullness:0x7ff 高5bitsp_adts_header[6] = 0xfc;      //‭11111100‬       //buffer fullness:0x7ff 低6bits// number_of_raw_data_blocks_in_frame://    表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧。return 0;
}// 程序本身 input.mp4  out.h264 out.aac
int main(int argc, char **argv)
{// 判断参数if(argc != 4) {printf("usage app input.mp4  out.h264 out.aac");return -1;}char *in_filename = argv[1];char *h264_filename = argv[2];char *aac_filename = argv[3];FILE *aac_fd = NULL;FILE *h264_fd = NULL;h264_fd = fopen(h264_filename, "wb");if(!h264_fd) {printf("fopen %s failedn", h264_filename);return -1;}aac_fd = fopen(aac_filename, "wb");if(!aac_fd) {printf("fopen %s failedn", aac_filename);return -1;}AVFormatContext *ifmt_ctx = NULL;int video_index = -1;int audio_index = -1;AVPacket *pkt = NULL;int ret = 0;char errors[ERROR_STRING_SIZE+1];  // 主要是用来缓存解析FFmpeg api返回值的错误stringifmt_ctx = avformat_alloc_context();if(!ifmt_ctx) {printf("avformat_alloc_context failedn");//        fclose(aac_fd);return -1;}ret = avformat_open_input(&ifmt_ctx, in_filename, NULL, NULL);if(ret < 0) {av_strerror(ret, errors, ERROR_STRING_SIZE);printf("avformat_open_input failed:%dn", ret);printf("avformat_open_input failed:%sn", errors);avformat_close_input(&ifmt_ctx);//        go failed;return -1;}video_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);if(video_index == -1) {printf("av_find_best_stream video_index failedn");avformat_close_input(&ifmt_ctx);return -1;}audio_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);if(audio_index == -1) {printf("av_find_best_stream audio_index failedn");avformat_close_input(&ifmt_ctx);return -1;}// h264_mp4toannexbconst AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");      // 对应面向对象的方法if(!bsfilter) {avformat_close_input(&ifmt_ctx);printf("av_bsf_get_by_name h264_mp4toannexb failedn");return -1;}AVBSFContext *bsf_ctx = NULL;        // 对应面向对象的变量ret = av_bsf_alloc(bsfilter, &bsf_ctx);if(ret < 0) {av_strerror(ret, errors, ERROR_STRING_SIZE);printf("av_bsf_alloc failed:%sn", errors);avformat_close_input(&ifmt_ctx);return -1;}ret = avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[video_index]->codecpar);if(ret < 0) {av_strerror(ret, errors, ERROR_STRING_SIZE);printf("avcodec_parameters_copy failed:%sn", errors);avformat_close_input(&ifmt_ctx);av_bsf_free(&bsf_ctx);return -1;}ret = av_bsf_init(bsf_ctx);if(ret < 0) {av_strerror(ret, errors, ERROR_STRING_SIZE);printf("av_bsf_init failed:%sn", errors);avformat_close_input(&ifmt_ctx);av_bsf_free(&bsf_ctx);return -1;}pkt = av_packet_alloc();av_init_packet(pkt);while (1) {ret = av_read_frame(ifmt_ctx, pkt);     // 不会去释放pkt的buf,如果我们外部不去释放,就会出现内存泄露if(ret < 0 ) {av_strerror(ret, errors, ERROR_STRING_SIZE);printf("av_read_frame failed:%sn", errors);break;}// av_read_frame 成功读取到packet,则外部需要进行buf释放if(pkt->stream_index == video_index) {// 处理视频ret = av_bsf_send_packet(bsf_ctx, pkt); // 内部把我们传入的buf转移到自己bsf内部if(ret < 0) {       // 基本不会进入该逻辑av_strerror(ret, errors, ERROR_STRING_SIZE);printf("av_bsf_send_packet failed:%sn", errors);av_packet_unref(pkt);continue;}
//            av_packet_unref(pkt); // 这里不需要去释放内存while (1) {ret = av_bsf_receive_packet(bsf_ctx, pkt);if(ret != 0) {break;}size_t size = fwrite(pkt->data, 1, pkt->size, h264_fd);if(size != pkt->size){av_log(NULL, AV_LOG_DEBUG, "h264 warning, length of writed data isn't equal pkt->size(%d, %d)n",size,pkt->size);}av_packet_unref(pkt);}} else if(pkt->stream_index == audio_index) {// 处理音频char adts_header_buf[7] = {0};adts_header(adts_header_buf, pkt->size,ifmt_ctx->streams[audio_index]->codecpar->profile,ifmt_ctx->streams[audio_index]->codecpar->sample_rate,ifmt_ctx->streams[audio_index]->codecpar->channels);fwrite(adts_header_buf, 1, 7, aac_fd);  // 写adts header , ts流不适用,ts流分离出来的packet带了adts headersize_t size = fwrite( pkt->data, 1, pkt->size, aac_fd);   // 写adts dataif(size != pkt->size){av_log(NULL, AV_LOG_DEBUG, "aac warning, length of writed data isn't equal pkt->size(%d, %d)n",size,pkt->size);}av_packet_unref(pkt);} else {av_packet_unref(pkt);       // 释放buffer}}printf("while finishn");
failed:if(h264_fd) {fclose(h264_fd);}if(aac_fd) {fclose(aac_fd);}if(pkt)av_packet_free(&pkt);if(ifmt_ctx)avformat_close_input(&ifmt_ctx);printf("Hello World!n");return 0;
}

八、AVIO

/**
* @projectName   07-05-decode_audio
* @brief         解码音频,主要的测试格式aac和mp3
* @author        Liao Qingfu
* @date          2020-01-16
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <libavutil/frame.h>
#include <libavutil/mem.h>#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>#define BUF_SIZE 20480static char* av_get_err(int errnum)
{static char err_buf[128] = {0};av_strerror(errnum, err_buf, 128);return err_buf;
}static void print_sample_format(const AVFrame *frame)
{printf("ar-samplerate: %uHzn", frame->sample_rate);printf("ac-channel: %un", frame->channels);printf("f-format: %un", frame->format);// 格式需要注意,实际存储到本地文件时已经改成交错模式
}static int read_packet(void *opaque, uint8_t *buf, int buf_size)
{FILE *in_file = (FILE *)opaque;int read_size = fread(buf, 1, buf_size, in_file);printf("read_packet read_size:%d, buf_size:%dn", read_size, buf_size);if(read_size <=0) {return AVERROR_EOF;     // 数据读取完毕}return read_size;
}static void decode(AVCodecContext *dec_ctx, AVPacket *packet, AVFrame *frame,FILE *outfile)
{int ret = 0;ret = avcodec_send_packet(dec_ctx, packet);if(ret == AVERROR(EAGAIN)) {printf("Receive_frame and send_packet both returned EAGAIN, which is an API violation.n");} else if(ret < 0) {printf("Error submitting the packet to the decoder, err:%sn",av_get_err(ret));return;}while (ret >= 0) {ret = avcodec_receive_frame(dec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {return;} else if (ret < 0)  {printf("Error during decodingn");exit(1);}if(!packet) {printf("get flush framen");}int data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);//        print_sample_format(frame);/**P表示Planar(平面),其数据格式排列方式为 :(每个LLLLLLRRRRRR为一个音频帧)而不带P的数据格式(即交错排列)排列方式为&#(每个LR为一个音频样本)播放范例:   ffplay -ar 48000 -ac 2 -f f32le believe.pcm并不是每一种都是这样的格式*/// 这里的写法不是通用,通用要调用重采样的函数去实现// 这里只是针对解码出来是planar格式的转换for(int i = 0; i < frame->nb_samples; i++) {for(int ch = 0; ch < dec_ctx->channels; ch++) {fwrite(frame->data[ch] + data_size *i, 1, data_size, outfile);}}}
}int main(int argc, char **argv)
{if(argc != 3) {printf("usage: %s <intput file> <out file>n", argv[0]);return -1;}const char *in_file_name = argv[1];const char *out_file_name = argv[2];FILE *in_file = NULL;FILE *out_file = NULL;// 1. 打开参数文件in_file = fopen(in_file_name, "rb");if(!in_file) {printf("open file %s failedn", in_file_name);return  -1;}out_file = fopen(out_file_name, "wb");if(!out_file) {printf("open file %s failedn", out_file_name);return  -1;}// 2自定义 iouint8_t *io_buffer = av_malloc(BUF_SIZE);AVIOContext *avio_ctx = avio_alloc_context(io_buffer, BUF_SIZE, 0, (void *)in_file,    read_packet, NULL, NULL);AVFormatContext *format_ctx = avformat_alloc_context();format_ctx->pb = avio_ctx;int ret = avformat_open_input(&format_ctx, NULL, NULL, NULL);if(ret < 0) {printf("avformat_open_input failed:%sn", av_err2str(ret));return -1;}// 编码器查找AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_AAC);if(!codec) {printf("avcodec_find_decoder failedn");return -1;}AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);if(!codec_ctx) {printf("avcodec_alloc_context3 failedn");return -1;}ret = avcodec_open2(codec_ctx, codec, NULL);if(ret < 0) {printf("avcodec_open2 failed:%sn", av_err2str(ret));return -1;}AVPacket *packet = av_packet_alloc();AVFrame *frame = av_frame_alloc();while (1) {ret = av_read_frame(format_ctx, packet);if(ret < 0) {printf("av_read_frame failed:%sn", av_err2str(ret));break;}decode(codec_ctx, packet, frame, out_file);}printf("read file finishn");decode(codec_ctx, NULL, frame, out_file);fclose(in_file);fclose(out_file);av_free(io_buffer);av_frame_free(frame);av_packet_free(packet);avformat_close_input(&format_ctx);avcodec_free_context(&codec_ctx);printf("main finishn");return 0;
}

本文发布于:2024-01-31 11:12:07,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170667072928095.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:编解码   FFmpeg   demo
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23