在有一些嵌入式平台中,H264数据流一般来自芯片内部的硬编码器, AAC音频数据则是通过采集PCM进行软编码,但是如何对它实时进行封装多媒体文件 ,参考ffmpeg example,花了一些时间终于实现了该功能。
流程图如下:
本文只展示DEMO
一.视频输入流 创建
//内存数据回调部分 static int read_packet(void *opaque, uint8_t *buf, int buf_size) { char * input_filename = (char *)opaque; static FILE *fl = NULL; if(fl == NULL){ fl = fopen(input_filename,"r"); } static unsigned long long read_len=0; static unsigned long long fps_count=0; int len=0; int i =0; if(!feof(fl)) len = fread(buf,1,buf_size,fl); else return AVERROR_EOF; read_len+= len; printf("%s len:%d read_len:%d\n",__FUNCTION__, len ,read_len); for(i=0;i<4091;i++){ if(buf[i+0] == 0 &&buf[i+1] == 0 &&buf[i+2] == 0 &&buf[i+3] == 1) { // int data = buf[i+4] &=31; printf("0 0 0 1 %x %d\n",buf[i+4],fps_count); fps_count++; } } return len; } static AVFormatContext * getInputVideoCtx(const char *fileName) { uint8_t *avio_ctx_buffer = NULL; AVIOContext *avio_ctx = NULL; //缓存buffersize size_t buffer_size, avio_ctx_buffer_size = 4096; AVFormatContext * video_fmt_ctx = NULL; int ret = 0; if (!(video_fmt_ctx = avformat_alloc_context())) { ret = AVERROR(ENOMEM); return NULL; } //创建数据缓存Buffer avio_ctx_buffer = av_malloc(avio_ctx_buffer_size); if (!avio_ctx_buffer) { ret = AVERROR(ENOMEM); return NULL; } avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, fileName, &read_packet, NULL, NULL); if (!avio_ctx) { ret = AVERROR(ENOMEM); return NULL; } video_fmt_ctx->pb = avio_ctx; //打开数据 ret = avformat_open_input(&video_fmt_ctx, NULL, NULL, NULL); if (ret < 0) { fprintf(stderr, "Could not open input\n"); return NULL; } //获取数据格式 ret = avformat_find_stream_info(video_fmt_ctx, NULL); if (ret < 0) { fprintf(stderr, "Could not find stream information\n"); return NULL; } //打印数据参数 av_dump_format(video_fmt_ctx, 0, fileName, 0); return video_fmt_ctx; }
1.注册内存回调read_packet,avformat_find_stream_info会从回调里读取大概2S的h264视频数据并解析。首先会读取SPS PPS,然后是帧数据,读取2S的数据结束,如果给的数据不对,解析不正常会一直读,所以要确保刚开始给的数据是否正常。av_dump_format打印出数据格式
执行如下:
二.创建多媒体输出,添加视频输出流音频输出流
avformat_alloc_output_context2(&oc, NULL, NULL, filename); ... // fmt = oc->oformat; if (fmt->video_codec != AV_CODEC_ID_NONE) { add_video_stream(&video_st, oc, video_fmt_ctx, fmt->video_codec); ... } /* Add the audio and video streams using the default format codecs * and initialize the codecs. */ if (fmt->audio_codec != AV_CODEC_ID_NONE) { add_audio_stream(&audio_st, oc, &audio_codec, fmt->audio_codec); ... }
1.添加视频流和初始化
/* media file output */ static void add_video_stream(OutputStream *ost, AVFormatContext *oc, const AVFormatContext *video_fmt_ctx, enum AVCodecID codec_id) { ... //创建一个输出流 ost->st = avformat_new_stream(oc, NULL); ... ost->st->id = oc->nb_streams-1; c = avcodec_alloc_context3(NULL); ... //流的time_base初始化 for (i = 0; i < video_fmt_ctx->nb_streams; i++) { if(video_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){ avcodec_parameters_to_context(c, video_fmt_ctx->streams[i]->codecpar); video_fmt_ctx->streams[i]->time_base.den = video_fmt_ctx->streams[i]->avg_frame_rate.num; } } //初始化av_packet ost->tmp_pkt = av_packet_alloc(); ... ost->enc = c; }
2.添加音频流 初始化编解码器
/* Add an output stream. */ static void add_audio_stream(OutputStream *ost, AVFormatContext *oc, const AVCodec **codec, enum AVCodecID codec_id) { *codec = avcodec_find_encoder(codec_id); ... //初始化有音频packet ost->tmp_pkt = av_packet_alloc(); ... //初始化流 ost->st = avformat_new_stream(oc, NULL); ... switch ((*codec)->type) { case AVMEDIA_TYPE_AUDIO: c->sample_fmt = (*codec)->sample_fmts ? (*codec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP; c->bit_rate = 64000; c->sample_rate = 44100;//采样率 if ((*codec)->supported_samplerates) { c->sample_rate = (*codec)->supported_samplerates[0]; for (i = 0; (*codec)->supported_samplerates[i]; i++) { if ((*codec)->supported_samplerates[i] == 44100) c->sample_rate = 44100; } } av_channel_layout_copy(&c->ch_layout, &(AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO); //输出audio流的time_base初始化 ost->st->time_base = (AVRational){ 1, c->sample_rate }; break; default: break; } }
3.初始化输出流音频和视频codecpar
static int open_video(AVFormatContext *oc, const AVCodec *codec,AVFormatContext *vedio_fmt_ctx, OutputStream *ost) { ... ret = avcodec_parameters_copy(ost->st->codecpar, vedio_fmt_ctx->streams[index]->codecpar); ... } static void open_audio(AVFormatContext *oc, const AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg) { ... /* copy the stream parameters to the muxer */ ret = avcodec_parameters_from_context(ost->st->codecpar, c); if (ret < 0) { fprintf(stderr, "Could not copy the stream parameters\n"); exit(1); } ... }
三.开始写入多媒体文件
1.比较写入音视频的时间戳,判断下一次要写入音频还是视频
while (encode_video) { /* select the stream to encode */ if (encode_video && ( !encode_audio || av_compare_ts(video_st.next_pts, video_fmt_ctx->streams[v_ctx_index]->time_base, audio_st.next_pts, audio_st.enc->time_base) <= 0)) { encode_video = !write_video_frame(oc, video_fmt_ctx, &video_st, video_st.tmp_pkt); } else { encode_audio = !write_audio_frame(oc, &audio_st); } }
av_compare_ts 通过对比当前Audio Video帧的写入量判断当前要写入Audio 还是Video
(例如: Video= 写入10帧* 1/25 > Audio 写入 10240*1/44100 则写入audio)
2.写入一帧Video
static int write_video_frame(AVFormatContext *oc,AVFormatContext *vic, OutputStream *ost, AVPacket *pkt) { int ret,i; static int frame_index = 0; AVStream *in_stream, *out_stream; int stream_index; stream_index = av_find_best_stream(vic, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); //读一帧H264 ret = av_read_frame(vic, pkt); if(ret == AVERROR_EOF) return ret == AVERROR_EOF ? 1 : 0; av_packet_rescale_ts(pkt, ost->enc->time_base, ost->st->time_base); if(pkt->pts==AV_NOPTS_VALUE){ in_stream = vic->streams[stream_index]; out_stream = ost->st; //Write PTS AVRational time_base1=in_stream->time_base; int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->avg_frame_rate); //计算出包的解码时间 pkt->pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE); pkt->dts=pkt->pts; pkt->duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE); //帧的计数累加 frame_index++; //pkt的pts dts是输入流的时间戳 要转换成 输出流的时间戳 av_packet_rescale_ts(pkt, in_stream->time_base, out_stream->time_base); pkt->pos = -1; pkt->stream_index=ost->st->index; } //写入到多媒体文件 ret = av_interleaved_write_frame(oc, pkt); if (ret < 0) { fprintf(stderr, "Error while writing output packet: %s\n", av_err2str(ret)); exit(1); } return ret == AVERROR_EOF ? 1 : 0; }
av_read_frame会回调read_packet 获取一帧H264数据,再通过计算时间戳 pts dts 再转换对应的输出流时间戳才能写入多媒体文件
3.写入一帧Audio
//获取一帧原始的Audio PCM 数据 /* Prepare a 16 bit dummy audio frame of 'frame_size' samples and * 'nb_channels' channels. */ static AVFrame *get_audio_frame(OutputStream *ost) { ... c = ost->enc; for (j = 0; jnb_samples; j++) { v = (int)(sin(ost->t) * 10000); for (i = 0; i < ost->enc->ch_layout.nb_channels; i++) *q++ = v; ost->t += ost->tincr; ost->tincr += ost->tincr2; } ... frame->pts = ost->next_pts; ost->next_pts += frame->nb_samples; count++; return frame; } static int write_audio_frame(AVFormatContext *oc, OutputStream *ost) { .... //获取一帧原始的Audio PCM 数据 frame = get_audio_frame(ost); if (frame) { dst_nb_samples = av_rescale_rnd(swr_get_delay(ost->swr_ctx, c->sample_rate) + frame->nb_samples, c->sample_rate, c->sample_rate, AV_ROUND_UP); ret = av_frame_make_writable(ost->frame); /* convert to destination format */ ret = swr_convert(ost->swr_ctx, ost->frame->data, dst_nb_samples, (const uint8_t **)frame->data, frame->nb_samples); frame = ost->frame; frame->pts = av_rescale_q(ost->samples_count, (AVRational){1, c->sample_rate}, c->time_base); ost->samples_count += dst_nb_samples; } //先送去编码再写入多媒体文件 return write_frame(oc, c, ost, frame, ost->tmp_pkt); } static int write_frame(AVFormatContext *fmt_ctx, AVCodecContext *c, OutputStream *ost, AVFrame *frame, AVPacket *pkt) { ... ret = avcodec_send_frame(c, frame); ... while (ret >= 0) { ret = avcodec_receive_packet(c, pkt); ... /* rescale output packet timestamp values from codec to stream timebase */ av_packet_rescale_ts(pkt, c->time_base, st->time_base); printf("%d %d\n", c->time_base.den, st->time_base.den); pkt->stream_index = st->index; ret = av_interleaved_write_frame(fmt_ctx, pkt); ... count++; } return ret == AVERROR_EOF ? 1 : 0; }
四.写入多媒体尾部结束:
av_write_trailer(oc);
一些BUG:
控制写入时间,可以在写入循环里添加break。写入数据过长会出现音视频不同步的情况,建议写入时间不超过30分钟
DEMO
有需要源码可以后台私信我
还没有评论,来说两句吧...