标题
ffmpeg 中录制的视频 iphone6s 能否播放的关键
clq
浏览(469) +
2021-02-23 09:11:28 发表
编辑
关键字:
[2021-02-23 12:57:46 最后更新]
c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; //我擦,这个是 iphone6s 能否播放的关键
clq
2021-02-23 12:55:27 发表
编辑
完整示例. #import "ffmpeg_record.h" #import "app_avi_analysis_ffmpeg.h" //以下 include 的顺序直接来自 ff_ffplay.c #include "libffmpeg/config.h" #include <inttypes.h> #include <math.h> #include <limits.h> #include <signal.h> #include <stdint.h> #include <fcntl.h> #include <sys/types.h> #include <unistd.h> #include "libavutil/avstring.h" #include "libavutil/eval.h" #include "libavutil/mathematics.h" #include "libavutil/pixdesc.h" #include "libavutil/imgutils.h" #include "libavutil/dict.h" #include "libavutil/parseutils.h" #include "libavutil/samplefmt.h" #include "libavutil/avassert.h" #include "libavutil/time.h" #include "libavformat/avformat.h" #if CONFIG_AVDEVICE #include "libavdevice/avdevice.h" #endif #include "libswscale/swscale.h" #include "libavutil/opt.h" #include "libavcodec/avfft.h" #include "libswresample/swresample.h" #if CONFIG_AVFILTER # include "libavcodec/avcodec.h" # include "libavfilter/avfilter.h" # include "libavfilter/buffersink.h" # include "libavfilter/buffersrc.h" #endif #include "ffmpeg_yuv_functions.h" #include "ffmpeg_record_functions.h" #import "app_avi_analysis.h" #include "ffmpeg_functions.h" #import "ViewController_main_tab.h" #import "GLSLViewController.h" #include "ffmpeg_audio.h" #include "functions_c.h" #import "Functions.h" //录像,包括摄像头和 rtsp struct ff_record_info //参考 struct st_ff_record { int mStarting; AVFormatContext *mInputAvFmtCtx; AVFormatContext *mOutputAvFmtCtx; AVBitStreamFilterContext *mH264bsfc; AVBitStreamFilterContext *mAACbsfc; //AVPacket mPacket; //这个应该是指未解压时的数据包 int mVIdx; int mAIdx; int mWidth; int mHeight; double mFps; int mSampleRate; //int mVideoFramesNum; const char * mInputUrl; const char * mOutputFile; char * tmp_fn; int is_stop; AVFormatContext * out_fmt_mp4;// = NULL; //clq add 指的是输出 mp4 时的包装器 AVCodecContext * out_video_encode; //clq add 生成 mp4 时需要的编码器,不一定是 h264 可以是 ff 自带的 mpeg4//视频编码器 AVCodecContext * out_audio_encode; //音频编码器. aac 或者 mp3 AVStream * out_video_stream; //生成的 mp4 文件中添加的视频流 AVStream * out_audio_stream; //生成的 mp4 文件中添加的视频流 int pts_frame_num_forVideo; //num_pts //临时的为计算 pts 放的变量 int pts_frame_num_forAudio; //num_pts //临时的为计算 pts 放的变量//音频的有可能不需要 pts int is_mp4file_create; //物理文件是否已经生成.或者说是压缩包是否可以写入流了. const char * filename_out; }; //统一的录像信息 struct record_info { int frame_width; int frame_height; int is_init; int is_init_video; int is_init_audio; struct ff_record_info ff; int write_frame_count_video; //已经写入了多少帧//视频 int write_frame_count_audio; //已经写入了多少帧//音频 int stop; //停止录像//目前测试用 }; //只有视频的情况下 void _record_image(struct record_info rec, struct image_info * img) { AVFrame * frame = img->__frame_ffmpeg; }// struct record_info grec = {0}; NSString * grec_url; //输入地址 NSString * grec_filename; //输出的文件名 //输出 ffmpeg 错误信息 void print_ff_error(int ret) { //ret = avcodec_open2(c, pCodecH264, NULL); if (ret<0) { //有失败的情况.比如是音频帧 //AVERROR_BSF_NOT_FOUND //http://www.voidcn.com/article/p-crlaihxf-btn.html //https://blog.csdn.net/henzhuanxin/article/details/83110494 //av_make_error_string((char[AV_ERROR_MAX_STRING_SIZE]){0}, AV_ERROR_MAX_STRING_SIZE, errnum) //av_strerror(); //printf("%s", av_err2str(ret)); char s[AV_ERROR_MAX_STRING_SIZE] = {0}; av_strerror(ret, s, AV_ERROR_MAX_STRING_SIZE-1); printf("ffmpeg error: %s", s); //return 0; } }// //录像有视频\音频 //https://www.jianshu.com/p/0d18f04e524d 有硬件编码的 int record_image_ffmpeg(AVFrame * frame, int is_audio) { record_audio_ffmpeg(frame, is_audio); return 0; //AVFrame * frame = img->__frame_ffmpeg; //_record_image(rec, img) NSString * avi_url = @"rtsp://192.168.1.217/stream0"; NSString * fn = AppPath_data(@"test1.mov"); //测试专用 grec_url = fn; grec_filename = AppPath_data(@"out1.mp4"); int out_fps = 25; //输出的视频 fps ,前期为了数据量,只设置为了 2, 一般 ios 就当为 25 const char * filename_out = c_str(grec_filename); //g_ff_record.tmp_fn; //"1.mp4"; struct record_info * rec = &grec; const char * url = c_str(grec_url); int ret = 0; //ffmpeg 操作的返回值 if (1 == is_audio) return 0; if (1 == rec->stop) return 0; rec->write_frame_count_video++; //参考 ff_record_open_input() if (0 == rec->is_init) { rec->is_init = 1; //不要再进入 //不删除好像不行 delete_file(grec_filename); //return 0; //-------- //1.首先要注册各个编解码器 // register_container_codec, register all codecs, demux and protocols avcodec_register_all(); av_register_all(); avformat_network_init(); if(rec->ff.mInputAvFmtCtx) { printf("already open input stream!\n"); return 0; } //2.直接指定编码器? rec->ff.mAACbsfc = av_bitstream_filter_init("aac_adtstoasc"); if (NULL == rec->ff.mAACbsfc) { printf("can not create aac_adtstoasc filter!\n"); return 0; } // h264有两种封装, 一种是annexb模式, 是传统模式, 有startcode(0x00000001), SPS和PPS是在ES中 // 另一种是AVCC模式, 一般用mp4、mkv、flv容器封装, 没有startcode, SPS和PPS以及其它信息被封装在container中 // 很多解码器只支持annexb这种模式, 因此需要将mp4模式转换成annexb模式 // ffmpeg读取mp4中的H264数据, 并不能直接得到NALU // 因此ffmpeg中提供了一个流过滤器"h264_mp4toannexb"可以完成这项工作 // 流过滤器"h264_mp4toannexb", 在av_register_all()函数中会被注册 //-------- //另一个说法 // ffmpeg中mp4转h264、h264_mp4toannexb、bsf使用说明及注意事项 //h264有两种封装,一种是annexb模式,传统模式,有startcode(0x000001或0x0000001)分割NALU,在mpegts流媒体中使用,vlc里打开编码器信息中显示h264; //一种是AVCC模式,一般用mp4、mkv、flv容器封装,以长度信息分割NALU, vlc里打开编码器信息中显示avc1。 //-------- rec->ff.mH264bsfc = av_bitstream_filter_init("h264_mp4toannexb"); if (NULL == rec->ff.mH264bsfc) { printf("can not create h264_mp4toannexb filter!\n"); return 0; } //3.应该是打开输入流//这个函数里应该是不用了的,不过保留吧 rec->ff.mInputAvFmtCtx = avformat_alloc_context(); rec->ff.mInputAvFmtCtx->flags |= AVFMT_FLAG_NONBLOCK; // open stream if (avformat_open_input(&rec->ff.mInputAvFmtCtx, url, NULL, NULL) != 0) { printf("avformat_open_input failed! [path]=[%s]\n", url); return 0; } //-------- //这部分比较复杂 //4.查找视频流,音频流的信息 // parse stream info if (avformat_find_stream_info(rec->ff.mInputAvFmtCtx, NULL) < 0) { printf("avformat_find_stream_info failed!\n"); return 0; } // find video stream rec->ff.mVIdx = av_find_best_stream(rec->ff.mInputAvFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if (rec->ff.mVIdx < 0) { printf("av_find_best_stream failed! (videoIdx %d)\n", rec->ff.mVIdx); return 0; } // find audio stream rec->ff.mAIdx = av_find_best_stream(rec->ff.mInputAvFmtCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); if (rec->ff.mAIdx < 0) { printf("av_find_best_stream failed! (audioIdx %d)\n", rec->ff.mAIdx); rec->ff.mAIdx = -1; } printf("av_find_best_stream (v %d, a %d)\n", rec->ff.mVIdx, rec->ff.mAIdx); av_dump_format(rec->ff.mInputAvFmtCtx, 0, url, 0); // 打印关于输入或输出格式的详细信息,例如持续时间,比特率,流,容器,程序,元数据,边数据,编解码器和时基。 //参数说明: //最后一个参数 is_output 选择指定的上下文是输入(0)还是输出(1),也就说最后一个参数填0,打印输入流;最后一个参数填1,打印输出流 //所以这个 av_dump_format 调用应该是可有可无的. //感觉这里和 av_find_best_stream 冲突了,这里遍历了所有的流,并找出视频和音频 for (int i = 0; i < rec->ff.mInputAvFmtCtx->nb_streams; i++) { AVStream *in_stream = rec->ff.mInputAvFmtCtx->streams[i]; printf("codec id: %d, URL: %s\n", in_stream->codec->codec_id, url); if (in_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO) { rec->ff.mVIdx = i; rec->ff.mWidth = in_stream->codec->width; //视频宽度 rec->ff.mHeight = in_stream->codec->height; //视频高度 if((in_stream->avg_frame_rate.den != 0) && (in_stream->avg_frame_rate.num != 0)) { AVRational avgFps = in_stream->avg_frame_rate; rec->ff.mFps = av_q2d(avgFps); //计算出了视频的 fps ? } printf("video stream index: %d, width: %d, height: %d, FrameRate: %.2f\n", rec->ff.mVIdx, rec->ff.mWidth, rec->ff.mHeight, rec->ff.mFps); } else if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO) { rec->ff.mAIdx = i; printf("audio stream index: %d\n", rec->ff.mAIdx); } } rec->ff.mInputUrl = url; //上面应该是完成大步骤 1 - 初始化并打开输入流,其实输入流动的打开应该再分出来 //--------------------------------------------------------------- //大步骤. 生成输出文件 //根据 ff_record_create_mp4_v1() 函数,因为生成文件时要设置编码器相关参数,所以要先生成压缩用的编码器,再生成文件 //生成编码器,这里应该还有很多要修改的 //AVCodecContext * MakeH264Encoder(AVFrame * en_frame1) //这是 h264 的,还有其他编码的 AVCodecContext * h264_encode; { //-------------------- //传递进来的编码器压缩失败,还是自己生成吧//要重新编译 ffmpeg AVCodec * pCodecH264 = avcodec_find_encoder(AV_CODEC_ID_H264); //mycodeid);//查找h264编码器//据说 需要引入独立的x264 库 //AVCodec * pCodecH264 = avcodec_find_encoder(AV_CODEC_ID_MJPEG); //不行 //AVCodec * pCodecH264 = avcodec_find_encoder(AV_CODEC_ID_MPEG4); //也不行//奇怪,代码库是跟着 ijkplayer 的,不是项目自己的 lib//要在 ffmpeg 中开放编码编译 //AVCodec * pCodecH264 = avcodec_find_encoder(AV_CODEC_ID_PNG); //也不行//奇怪,代码库是跟着 ijkplayer 的,不是项目自己的 lib if (!pCodecH264) { fprintf(stderr, "h264 codec not found\n"); exit(1); } int width = frame->width;//vwidth; int height = frame->height;//vheight; AVCodecContext * c = avcodec_alloc_context3(pCodecH264);//函数用于分配一个AVCodecContext并设置默认值,如果失败返回NULL,并可用av_free()进行释放 c->bit_rate = 400000; //设置采样参数,即比特率 c->width = width;//设置编码视频宽度 c->height = height; //设置编码视频高度 c->time_base.num = 1; c->time_base.den = out_fps; //2;//设置帧率,num为分子和den为分母,如果是1/25则表示25帧/s c->gop_size = 10; //设置GOP大小,该值表示每10帧会插入一个I帧 c->max_b_frames = 1;//设置B帧最大数,该值表示在两个非B帧之间,所允许插入的B帧的最大帧数 c->pix_fmt = AV_PIX_FMT_YUV420P;//PIX_FMT_YUV420P;//设置像素格式 //"AVRational -- 本质上用来表示有理数,有一些帧率frame_rate[每秒出现多少帧]和frame time一帧多少时间(和帧率互为倒数)" //c->framerate = (AVRational){2,1}; //所以还是应该设置 framerate 帧率 的 c->framerate = (AVRational){out_fps, 1}; //所以还是应该设置 framerate 帧率 的 //-------- //为兼容 ios 添加的参数 NSLog(@"c->compression_level:%d", c->compression_level); //c->compression_level = 3.0; //压缩级别? //https://www.jianshu.com/p/31f0593496ef //似乎对生成的文件无影响 NSLog(@"c->level:%d", c->level); //->level = 3.0; //会打开失败 //c->level = 4.0; NSLog(@"c->profile:%d", c->profile); c->profile = FF_PROFILE_H264_BASELINE; //据说这个不行,要用 av_dict_set //https://blog.csdn.net/kisaa133/article/details/7792008 AVDictionary *opts = NULL; av_dict_set(&opts, "profile", "baseline", 0); /* open the codec */ //if (avcodec_open2(m_pEncoderCtx, encoder, &opts) < 0) //根据 https://blog.csdn.net/holylts/article/details/7802610 的代码.这应该和 av_opt_set(codectx->priv_data, "preset", "slow", 0); 这种设置方法是一样的效果 //codectx->profile(设置此属性没有作用) //codectx->level = 13; //if(codecid == CODEC_ID_H264) //{ // av_opt_set(codectx->priv_data, "preset", "slow", 0); // av_opt_set(codectx->priv_data, "profile", "baseline", 0); //} //codectx->profile(设置此属性没有作用) //c->level = 13; //这个才是 压缩级别. 13 的话就是 1.3 所以 ios 的 3.0 应该就是 30 c->level = 30; //这个才是 压缩级别. 13 的话就是 1.3 所以 ios 的 3.0 应该就是 30 if(c->codec_id == AV_CODEC_ID_H264) { av_dict_set(&opts, "preset", "slow", 0); //av_dict_set(&opts, "profile", "baseline", 0); av_dict_set(&opts, "profile", "high", 0); } //这几个应该没起作用 //而且应该不是这个原因,参考 out1-12_1611135279609603.mp4 也是 isom 的头一样能播放 av_dict_set(&opts, "major_brand", "mp42", 0); av_dict_set(&opts, "minor_version","512" , 0); av_dict_set(&opts, "compatible_brands","isomiso2avc1mp41",0); //c->bit_rate = 400000; //设置采样参数,即比特率 //c->bit_rate = 0.96*1000000; c->bit_rate = 0.46*1000000; //https://www.meiwen.com.cn/subject/pjbfectx.html //代表设置编码后的平均码率,对于h264编码,分辨率对应的推荐码率参考网址:h264编码码率对应表 //http://www.lighterra.com/papers/videoencodingh264/ //https://www.jianshu.com/p/4da1265fa196 -- 1080p 为 4.992 mbps, 对应的音频为 128 或 320 kbps //注意二者的单位是不同的 //c->bit_rate = 3.84 * 1000000; //ok c->bit_rate = 4.992 * 1000000; //https://www.meiwen.com.cn/subject/heosqttx.html 中的内容也可以参考对参数的细调 ////c->width = 640;//设置编码视频宽度 //ok ////c->height = 368; //设置编码视频高度 //ok c->width = frame->width;//设置编码视频宽度 c->height = frame->height; //设置编码视频高度 //-------- av_opt_set(c->priv_data, "tune", "zerolatency", 0);//设置编码器的延时,解决前面的几十帧不出数据的情况 //-------- //要在 avcodec_open2 之前调用,所以放到这里来 // 某些封装格式必须要设置,否则会造成封装后文件中信息的缺失 //if (out_fmt->oformat->flags & AVFMT_GLOBALHEADER) { c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; //我擦,这个是 iphone6s 能否播放的关键 //} // x264编码特有 //if (en_codec->id == AV_CODEC_ID_H264) { if (c->codec->id == AV_CODEC_ID_H264) { // 代表了编码的速度级别 av_opt_set(c->priv_data,"preset","slow",0); //越慢速度越好. 参考 https://www.cnblogs.com/ingbyr/p/13327606.html //所以最好的质量应该是 "veryslow" c->flags2 = AV_CODEC_FLAG2_LOCAL_HEADER; //https://www.meiwen.com.cn/subject/pjbfectx.html //遇到问题:将H264视频码流封装到MP4中后,无法播放; //解决方案:加入如下代码 //Some formats want stream headers to be separate } //-------- //根据前面的 AV_CODEC_FLAG_GLOBAL_HEADER 说明,其实和选定后的文件格式还是有关的,所以正规来说还是要在文件格式生成后再设置 ret = avcodec_open2(c, pCodecH264, &opts); if (ret<0) { //有失败的情况.比如是音频帧 //输出 ffmpeg 错误信息 print_ff_error(ret); return 0; } //-------------------- //return c; h264_encode = c; NSLog(@"c->compression_level:%d", c->compression_level); }//h264 //--------------------------------------------------------------- //大步骤. 生成输出文件 . 根据编码器生成文件 //AVFormatContext * MakeMp4File_h264_v2(AVCodecContext * en_ctx, const char * fn, AVStream ** avs) //AVFormatContext * out_fmt_ctx = NULL; AVFormatContext * out_fmt = NULL; //AVCodecContext * en_ctx = h264_encode; AVCodecContext * out_encode = h264_encode; // 创建mp4文件封装器 ret = avformat_alloc_output_context2(&out_fmt, NULL, NULL, filename_out); if (ret < 0) { //LOGD("MP2 muxer fail"); //releaseSources(); print_ff_error(ret); return 0; } //g_ff_record.ou_fmt_mp4 = ou_fmt; //存一下变量 // 添加视频流 AVStream *stream = avformat_new_stream(out_fmt, NULL); //int video_out_index = stream->index; //*avs = stream; rec->ff.out_video_stream = stream; //--------------------------- //不设置视频流参数是不行的? // 设置编码参数 //AVStream *in_stream = in_fmt->streams[video_index]; //en_ctx->width = in_stream->codecpar->width; //en_ctx->height = in_stream->codecpar->height; //en_ctx->pix_fmt = (enum AVPixelFormat)in_stream->codecpar->format; //en_ctx->bit_rate = 0.96*1000000; //en_ctx->framerate = (AVRational){5,1}; //en_ctx->time_base = (AVRational){1,5}; // 某些封装格式必须要设置,否则会造成封装后文件中信息的缺失 if (out_fmt->oformat->flags & AVFMT_GLOBALHEADER) { out_encode->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } // x264编码特有 //if (en_codec->id == AV_CODEC_ID_H264) { if (out_encode->codec->id == AV_CODEC_ID_H264) { // 代表了编码的速度级别 av_opt_set(out_encode->priv_data,"preset","slow",0); out_encode->flags2 = AV_CODEC_FLAG2_LOCAL_HEADER; } //应该要在 avcodec_open2 之前,而这里 avcodec_open2 前面已经调用过了 // 初始化编码器及编码器上下文 //if (avcodec_open2(en_ctx,en_codec,NULL) < 0) { // //LOGD("encodec ctx fail"); // //releaseSources(); // return; //} // 设置视频流参数;对于封装来说,直接从编码器上下文拷贝即可 if (avcodec_parameters_from_context(stream->codecpar, out_encode) < 0) { printf("copy en_code parameters fail"); //releaseSources(); return 0; } // 初始化封装器输出缓冲区 if (!(out_fmt->oformat->flags & AVFMT_NOFILE)) { if (avio_open2(&out_fmt->pb, filename_out, AVIO_FLAG_WRITE, NULL, NULL) < 0) { printf("avio_open2 fail"); //releaseSources(); return 0; } } // 写入封装器头文件信息;此函数内部会对封装器参数做进一步初始化 //https://blog.csdn.net/Poisx/article/details/79142727 //奇怪, ios 能播放的就应该是上面的那样,但我们这里生成的不是 //https://www.jianshu.com/p/ab2e44f5ea82 处的硬解文章中也有一部分说明 //这样生成的为 isom 而苹果要的是 mp42 ,同时这种情况下头 4 个字节为 00 00 00 20 而苹果的是 00 00 00 1c //所以应该想办法将 isom 转换为 mp42 //-------- //https://www.javaroad.cn/questions/81137 似乎可以直接修改 //没效果 //AVDictionary *opt = out_fmt->metadata; //av_dict_set(&opt, "major_brand", "mp42", 0); //av_dict_set(&opt, "minor_version","512" , 0); //av_dict_set(&opt, "compatible_brands","isomiso2avc1mp41",0); //av_dict_set(&opt, "comment","Hash=855738390",0); //out_fmt->metadata = opt; //-------- //NvsStreamingContext 原来的 app 用的这个,应该是 美摄SDK //-------- if (avformat_write_header(out_fmt, NULL) < 0) { printf("avformat_write_header fail"); //releaseSources(); return 0; } //return ou_fmt; //AVFormatContext * ou_fmt rec->ff.mOutputAvFmtCtx = out_fmt; rec->ff.out_fmt_mp4 = out_fmt; rec->ff.out_video_encode = out_encode; }//is_init //if 初始化 //--------------------------------------------------------------- //大步骤. 压缩 //void _doEncode(AVFrame *en_frame1) //for (int i=0; i<600; i++) //写入 60 帧空白图 int r = 0; //-------- // 编码前要设置好pts的值,如果en_ctx->time_base为{1,fps},那么这里pts的值即为帧的个数值 //不设置这个的话,第二帧编码时就会出错 // frame->pts = num_pts++; frame->pts = rec->ff.pts_frame_num_forVideo++; //如何强制ffmpeg编码时输出一个关键帧 //在编码前设置//确实可以,不过这样会让文件很大,除非是要发送给对方,希望马上显示时//不影响苹果播放兼容性 ////blankFrame->pict_type = AV_PICTURE_TYPE_I; //FF_I_TYPE; ////blankFrame->key_frame=1; //-------------------- r = avcodec_send_frame(rec->ff.out_video_encode, frame); //用自己生成的编码器 if (r != 0) { printf("编码失败:%d.", r); print_ff_error(r); } while (1) { AVPacket *ou_pkt = av_packet_alloc(); if (avcodec_receive_packet(rec->ff.out_video_encode, ou_pkt) < 0) //换用自己的编码器 { av_packet_unref(ou_pkt); //奇怪,老是返回 AVERROR(EAGAIN); -- 是因为没有 pts //第一帧,未必能写入,据说这是表示需要更多的 avcodec_send_frame 加入图片 break; }//if // 成功编码了;写入之前要进行时间基的转换 //参考 https://blog.csdn.net/u011588166/article/details/94384481 //转换前的时间基 - 即本帧的时间位置是以帧为单位的 //转换后的时间基 - 即本帧的时间位置是以压缩后的字节流位置为单位的 //所以要用 frame 帧播放时间计算出 packet 压缩包的播放时间 //"压缩后的数据(对应的结构体为AVPacket)对应的时间基为AVStream的time_base,AVRational{1,90000}" //另外一个比较乱的解释 //AVStream *stream = ou_fmt->streams[video_ou_index]; //av_packet_rescale_ts(ou_pkt, en_ctx->time_base, stream->time_base); //LOGD("video pts %d(%s)",ou_pkt->pts,av_ts2timestr(ou_pkt->pts, &stream->time_base)); //av_write_frame(ou_fmt, ou_pkt); //输出后不用释放? 由流自己释放吗? ////av_packet_rescale_ts(ou_pkt, h264encodec->time_base, h264encodec->time_base); //这样有用吗? //不对,一般第二个参数要使用 av 流的的 time_base ,而不是编码器的.所以还是要把生成的 mp4 文件的视频流指针引出来 //// av_packet_rescale_ts(ou_pkt, rec->ff.out_encode->time_base, avs->time_base); //应该是这个,但前提应该是 avs 本身是对的 //参数是输出编码器和输出流的 time_base (帧率) av_packet_rescale_ts(ou_pkt, rec->ff.out_video_encode->time_base, rec->ff.out_video_stream->time_base); //---- //奇怪,生成的文件并没有什么不同//https://www.cnblogs.com/vczf/p/13818609.html //转换成兼容格式,就是前面提到的什么 "h264_mp4toannexb" //据说 FLV/MP4/MKV等结构中,h264需要h264_mp4toannexb处理。添加SPS/PPS等信息。 ret = av_bitstream_filter_filter(rec->ff.mH264bsfc, rec->ff.out_video_encode, //_this->mInputAvFmtCtx->streams[_this->mVIdx]->codec, NULL, &ou_pkt->data, &ou_pkt->size, ou_pkt->data, ou_pkt->size, 0); if (r != 0) { printf("编码过滤转换时失败:%d.", r); print_ff_error(r); } //---- ////ou_pkt.stream_index = 0; //不需要指定吗? av_write_frame(rec->ff.out_fmt_mp4, ou_pkt); //输出后不用释放? 由流自己释放吗? //这个和 av_interleaved_write_frame 有什么区别 } //--------------------------------------------------------------- //大步骤. 写入一帧 av_interleaved_write_frame //写入帧和压缩过程比较紧密,所以还是放在压缩过程里吧. //而且压缩时可能是要一次取几个包的.而写入帧其实只有一句话 av_write_frame() 所以实际上也不用独立出来 //--------------------------------------------------------------- //结束了. 据说一定要写入文件尾. //void FFRecordMP4::closeInputStream(void) { //void ff_record_close_input(void) //这只是关闭输入流 if(0) { //---- if (NULL != rec->ff.mInputAvFmtCtx) { // release resource avformat_close_input(&rec->ff.mInputAvFmtCtx); rec->ff.mInputAvFmtCtx = NULL; } if (NULL != rec->ff.mH264bsfc) { av_bitstream_filter_close(rec->ff.mH264bsfc); rec->ff.mH264bsfc = NULL; } if (NULL != rec->ff.mAACbsfc) { av_bitstream_filter_close(rec->ff.mAACbsfc); rec->ff.mAACbsfc = NULL; } }// //-------- //写入文件尾等 if (rec->write_frame_count_video > 101) //测试 100 帧先吧 { rec->stop = 1; av_write_trailer(rec->ff.out_fmt_mp4); //写入文件尾 //释放各种资源 //ff_free_avframe_direct(blankFrame); if (rec->ff.out_fmt_mp4) { avformat_free_context(rec->ff.out_fmt_mp4); rec->ff.out_fmt_mp4 = NULL; } if (rec->ff.out_video_encode) { avcodec_free_context(&rec->ff.out_video_encode); rec->ff.out_video_encode = NULL; } }// return 1; }// void record_image(struct image_info * img) { //AVFrame * frame = img->__frame_ffmpeg; }// //void ff_record_create_mp4_v1(const char * fn) 的详细参数调整 //--------------------------------------------------------------- //示例源码中的三个辅助函数 //ffmpeg 中的源码 encode_audio.c /* check that a given sample format is supported by the encoder */ //就当是检查编码器是否支持这种格式的音频 static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt) { const enum AVSampleFormat *p = codec->sample_fmts; while (*p != AV_SAMPLE_FMT_NONE) { if (*p == sample_fmt) return 1; p++; } return 0; } /* just pick the highest supported samplerate */ //应当是选择当前编码器的最高质量//直接给 44100 其实也是可以的 static int select_sample_rate(const AVCodec *codec) { const int *p; int best_samplerate = 0; if (!codec->supported_samplerates) return 44100; p = codec->supported_samplerates; while (*p) { if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate)) best_samplerate = *p; p++; } return best_samplerate; } /* select layout with the highest channel count */ //static int select_channel_layout(const AVCodec *codec) static uint64_t select_channel_layout(const AVCodec *codec) { const uint64_t *p; uint64_t best_ch_layout = 0; int best_nb_channels = 0; if (!codec->channel_layouts) return AV_CH_LAYOUT_STEREO; p = codec->channel_layouts; while (*p) { int nb_channels = av_get_channel_layout_nb_channels(*p); if (nb_channels > best_nb_channels) { best_ch_layout = *p; best_nb_channels = nb_channels; } p++; } return best_ch_layout; } //--------------------------------------------------------------- //音频的 //根据各方知识,目前来说编码的 AVFrame 目前的 ACC 编码器,如果源不是 AV_SAMPLE_FMT_FLTP 而是更老的 AV_SAMPLE_FMT_S16 //要先转换为 AV_SAMPLE_FMT_FLTP , 所以应当是源数据不为这两种格式的话就要先转换.转换代码看我们前面播放的地方,转换为苹果专有 //格式时已经是成功的了 //ok 音频成功.原理大家说得很复杂.不过最后成功的是使用实测的数据反推算.主要的计算单位是原始数据帧的 nb_samples (例如 1024) //至少在编码器的 timebase 的分子为 1 时. frame 的 pts 累加单位不是视频帧的 1 而就是 nb_samples //压缩包最后的 pts=dts=frame.pts 这是非常奇怪的,与视频帧中的完全不同.同时压缩包的 duration 也直接等于 始数据帧的 nb_samples //而这些计算结果和压缩比率完全无关. 也就是说 pts 这些至少对音频来说只与原始数据有关,和压缩后的数据完全无关. //那么 nb_samples 到底是什么呢? 可能可以理解为每秒采集多少个数据.与视频每秒采集多个图像帧类似,而图像的帧就对应为一个 frame 了. //音频中似乎不是.比如视频中一帧就是 1/25 的数据,而音频则似乎不是的.以后再详细研究了. //不过这里的 frame 直接来自另外一个 ffmpeg 的文件,如果是摄像头的应该我更新写一个计算函数. //参考 https://blog.csdn.net/u010246197/article/details/54926455 mp3 的时候数值是不一样的.另外,这个是固定码率的 //动态码率的可能算法不同 int record_audio_ffmpeg(AVFrame * frame, int is_audio) { //AVFrame * frame = img->__frame_ffmpeg; struct record_info * rec = &grec; int ret = 0; //ffmpeg 操作的返回值 //if (1 == is_audio) return 0; if (0 == is_audio) return 0; //只要音频 if (1 == rec->stop) return 0; rec->write_frame_count_audio++; //参考 ff_record_open_input() //初始化 ffmpeg 环境及 mp4 文件结构. //注意还没有写入文件头,而且以后应该是要将 ffmpeg 的环境初始化分离出去的,因为实际 //操作时是有可能多次生成文件的. if (0 == rec->is_init) { rec->is_init = 1; //不要再进入 //--------------------------------------------------------------- NSString * avi_url = @"rtsp://192.168.1.217/stream0"; NSString * fn = AppPath_data(@"test1.mov"); //测试专用 grec_url = fn; grec_filename = AppPath_data(@"out1.mp4"); int out_fps = 25; //输出的视频 fps ,前期为了数据量,只设置为了 2, 一般 ios 就当为 25 const char * filename_out = c_str(grec_filename); //g_ff_record.tmp_fn; //"1.mp4"; rec->ff.filename_out = filename_out; const char * url = c_str(grec_url); //--------------------------------------------------------------- //不删除好像不行 delete_file(grec_filename); //return 0; //-------- //1.首先要注册各个编解码器 // register_container_codec, register all codecs, demux and protocols avcodec_register_all(); av_register_all(); avformat_network_init(); if(rec->ff.mInputAvFmtCtx) { printf("already open input stream!\n"); return 0; } //2.直接指定编码器? rec->ff.mAACbsfc = av_bitstream_filter_init("aac_adtstoasc"); if (NULL == rec->ff.mAACbsfc) { printf("can not create aac_adtstoasc filter!\n"); return 0; } //-------- //另一个说法 // ffmpeg中mp4转h264、h264_mp4toannexb、bsf使用说明及注意事项 //h264有两种封装,一种是annexb模式,传统模式,有startcode(0x000001或0x0000001)分割NALU,在mpegts流媒体中使用,vlc里打开编码器信息中显示h264; //一种是AVCC模式,一般用mp4、mkv、flv容器封装,以长度信息分割NALU, vlc里打开编码器信息中显示avc1。 //-------- rec->ff.mH264bsfc = av_bitstream_filter_init("h264_mp4toannexb"); if (NULL == rec->ff.mH264bsfc) { printf("can not create h264_mp4toannexb filter!\n"); return 0; } //3.应该是打开输入流//这个函数里应该是不用了的,不过保留吧 rec->ff.mInputAvFmtCtx = avformat_alloc_context(); rec->ff.mInputAvFmtCtx->flags |= AVFMT_FLAG_NONBLOCK; // open stream if (avformat_open_input(&rec->ff.mInputAvFmtCtx, url, NULL, NULL) != 0) { printf("avformat_open_input failed! [path]=[%s]\n", url); return 0; } //-------- //这部分比较复杂 //4.查找视频流,音频流的信息 // parse stream info if (avformat_find_stream_info(rec->ff.mInputAvFmtCtx, NULL) < 0) { printf("avformat_find_stream_info failed!\n"); return 0; } // find video stream rec->ff.mVIdx = av_find_best_stream(rec->ff.mInputAvFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if (rec->ff.mVIdx < 0) { printf("av_find_best_stream failed! (videoIdx %d)\n", rec->ff.mVIdx); return 0; } // find audio stream rec->ff.mAIdx = av_find_best_stream(rec->ff.mInputAvFmtCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); if (rec->ff.mAIdx < 0) { printf("av_find_best_stream failed! (audioIdx %d)\n", rec->ff.mAIdx); rec->ff.mAIdx = -1; } printf("av_find_best_stream (v %d, a %d)\n", rec->ff.mVIdx, rec->ff.mAIdx); av_dump_format(rec->ff.mInputAvFmtCtx, 0, url, 0); // 打印关于输入或输出格式的详细信息,例如持续时间,比特率,流,容器,程序,元数据,边数据,编解码器和时基。 //参数说明: //最后一个参数 is_output 选择指定的上下文是输入(0)还是输出(1),也就说最后一个参数填0,打印输入流;最后一个参数填1,打印输出流 //所以这个 av_dump_format 调用应该是可有可无的. //感觉这里和 av_find_best_stream 冲突了,这里遍历了所有的流,并找出视频和音频 for (int i = 0; i < rec->ff.mInputAvFmtCtx->nb_streams; i++) { AVStream *in_stream = rec->ff.mInputAvFmtCtx->streams[i]; printf("codec id: %d, URL: %s\n", in_stream->codec->codec_id, url); if (in_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO) { rec->ff.mVIdx = i; rec->ff.mWidth = in_stream->codec->width; //视频宽度 rec->ff.mHeight = in_stream->codec->height; //视频高度 if((in_stream->avg_frame_rate.den != 0) && (in_stream->avg_frame_rate.num != 0)) { AVRational avgFps = in_stream->avg_frame_rate; rec->ff.mFps = av_q2d(avgFps); //计算出了视频的 fps ? } printf("video stream index: %d, width: %d, height: %d, FrameRate: %.2f\n", rec->ff.mVIdx, rec->ff.mWidth, rec->ff.mHeight, rec->ff.mFps); } else if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO) { rec->ff.mAIdx = i; printf("audio stream index: %d\n", rec->ff.mAIdx); } } rec->ff.mInputUrl = url; //上面应该是完成大步骤 1 - 初始化并打开输入流,其实输入流动的打开应该再分出来 //--------------------------------------------------------------- //大步骤. 生成输出文件 //根据 ff_record_create_mp4_v1() 函数,因为生成文件时要设置编码器相关参数,所以要先生成压缩用的编码器,再生成文件 //生成编码器,这里应该还有很多要修改的 //--------------------------------------------------------------- //大步骤. 生成输出文件 . 根据编码器生成文件 AVFormatContext * out_fmt = NULL; //AVCodecContext * out_encode = h264_encode; // 创建mp4文件封装器 ret = avformat_alloc_output_context2(&out_fmt, NULL, NULL, filename_out); if (ret < 0) { print_ff_error(ret); return 0; } //-------- //return ou_fmt; //AVFormatContext * ou_fmt rec->ff.mOutputAvFmtCtx = out_fmt; rec->ff.out_fmt_mp4 = out_fmt; //rec->ff.out_encode = out_encode; }//is_init //if 初始化 //初始化音频流 if (0 == rec->is_init_audio) { rec->is_init_audio = 1; //不要再进入 //https://blog.csdn.net/cc_xueqin/article/details/41849539 AVCodecContext * aac_encode = NULL; //ACC LC 是什么编码器弄出来的? //AV_CODEC_ID_MP3, //AV_CODEC_ID_AAC, //AVCodec * pCodecAac = avcodec_find_encoder(AV_CODEC_ID_H264); //查找编码器 AVCodec * pCodecAac = avcodec_find_encoder(AV_CODEC_ID_AAC); //查找编码器 --enable-encoders 后也有,大概是有默认的 aac 编码器 //AVCodec * pCodecAac = avcodec_find_encoder(AV_CODEC_ID_OPUS); //enable-encoders 后也有 //AVCodec * pCodecAac = avcodec_find_encoder(AV_CODEC_ID_MP3); //没有.即使 enable-encoders //AVCodec * pCodecAac = avcodec_find_encoder(AV_CODEC_ID_MP2); //--enable-encoders 后可以用这个 //doc/examples/encode_audio.c 中的例子 //http://www.manongjc.com/article/61294.html 也有例子 //按道理应该是 --enable-libfaac --enable-libfdk-aac 不过前者编译时并不支持,大概是版本原因 ///Users/horseming/Desktop/test1/3rd/ijkplayer-master/ijkplayer-ios/ios/ffmpeg-armv7/doc/examples/encode_video.c ///Users/horseming/Desktop/test1/3rd/ijkplayer-master/ijkplayer-ios/ios/ffmpeg-armv7/doc/examples/encode_audio.c //从以上源码看,音频的压缩反而复杂得多 if (!pCodecAac) { fprintf(stderr, "acc codec not found\n"); exit(1); } AVCodecContext * c = avcodec_alloc_context3(pCodecAac);//函数用于分配一个AVCodecContext并设置默认值,如果失败返回NULL,并可用av_free()进行释放 //定义AAC音频格式采样率,采样格式,音频格式,比特率,声道。 c->codec_type = AVMEDIA_TYPE_AUDIO; c->codec_id = AV_CODEC_ID_AAC; c->sample_fmt = AV_SAMPLE_FMT_FLTP; //dst_sample_fmt; //AV_SAMPLE_FMT_FLTP //据说,目前的 ACC 编码器,如果源不是 AV_SAMPLE_FMT_FLTP 而是更老的 AV_SAMPLE_FMT_S16 //这样的,就要先转换 c->sample_fmt = frame->format; //应该是按源数据的.不是 AV_SAMPLE_FMT_FLTP 的话要先转换 NSLog(@"c->sample_fmt: %d, AV_SAMPLE_FMT_FLTP: %d", c->sample_fmt, AV_SAMPLE_FMT_FLTP); //c->sample_rate = 44100; //sample_rate;//44100 c->sample_rate = frame->sample_rate; //sample_rate;//44100 //这个似乎指的是源数据的采样率? 不能随意设置? NSLog(@"frame->sample_rate: %d", frame->sample_rate); c->channels = frame->channels;//2; //nb_channels;//2 NSLog(@"frame->channels:%d", frame->channels); c->bit_rate = 64000; // 64000; //96000; c->channel_layout = frame->channel_layout; //ch_layout; //什么参数? c->profile = FF_PROFILE_AAC_LOW; //(可参考AAC格式简介) c->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; //这一对应该也是可以的 //c->channel_layout = AV_CH_LAYOUT_STEREO; //立体声 //c->channels = av_get_channel_layout_nb_channels(c->channel_layout); //更复杂的要看 select_channel_layout() 选择最高的支持方式. 这里是取源数据的 //参数还是很重要的.例如一个错误为 Channel layout 'mono' with 1 channels does not match number of specified channels 2 //直接使用 frame->channel_layout 和 frame->channels 就可以避免这个错误 /* check that the encoder supports s16 pcm input */ //c->sample_fmt = AV_SAMPLE_FMT_S16; //内置的 AAC 编码器确实不支持它 if (!check_sample_fmt(pCodecAac, c->sample_fmt)) { fprintf(stderr, "Encoder does not support sample format %s", av_get_sample_fmt_name(c->sample_fmt)); exit(1); } //-------- //http://www.manongjc.com/article/61294.html //新版大于3.0后的版本,按雷霄骅大神的 最简单的基于FFMPEG的音频编码器(PCM编码为AAC)的例子已经无法走完流程,avcodec_open2会返回-22,其中原因是 // //1、新版版的ffmpeg 编码AAC只支持的AV_SAMPLE_FMT_FLTP,老版本的是AV_SAMPLE_FMT_S16,参数开之后avcodec_open2返回正确,但不要高兴太早; // //2、很可能你传入的PCM数据是AV_SAMPLE_FMT_S16的,avcodec_encode_audio2还是返回-22错误,争取做法是需要AV_SAMPLE_FMT_S16的数据转为AV_SAMPLE_FMT_FLTP就可以了 //-------- //为兼容 ios 添加的参数 //c->profile = FF_PROFILE_H264_BASELINE; //据说这个不行,要用 av_dict_set //AVDictionary *opts = NULL; //av_dict_set(&opts, "profile", "baseline", 0); //这应该和 av_opt_set(codectx->priv_data, "profile", "baseline", 0); //c->bit_rate = 400000; //设置采样参数,即比特率 c->bit_rate = 0.46*1000000; //https://www.meiwen.com.cn/subject/pjbfectx.html //代表设置编码后的平均码率,对于h264编码,分辨率对应的推荐码率参考网址:h264编码码率对应表 //http://www.lighterra.com/papers/videoencodingh264/ //https://www.jianshu.com/p/4da1265fa196 -- 1080p 为 4.992 mbps, 对应的音频为 128 或 320 kbps //注意二者的单位是不同的 //c->bit_rate = 4.992 * 1000000; c->bit_rate = 320 * 1000; c->bit_rate = 64000; //test //https://www.meiwen.com.cn/subject/heosqttx.html 中的内容也可以参考对参数的细调 //-------- //c->flags = i->flags; c->flags |= CODEC_FLAG_GLOBAL_HEADER; c->time_base.num = 1;//i->time_base.num; c->time_base.den = c->sample_rate;//i->time_base.den; //-------- //av_opt_set(c->priv_data, "tune", "zerolatency", 0);//设置编码器的延时,解决前面的几十帧不出数据的情况 //要在 avcodec_open2 之前调用,所以放到这里来 //根据前面的 AV_CODEC_FLAG_GLOBAL_HEADER 说明,其实和选定后的文件格式还是有关的,所以正规来说还是要在文件格式生成后再设置 //ret = avcodec_open2(c, pCodecAac, &opts); ret = avcodec_open2(c, pCodecAac, NULL); if (ret<0) { //有失败的情况.比如是音频帧 //输出 ffmpeg 错误信息 print_ff_error(ret); return 0; } //-------------------- //return c; //h264_encode = c; aac_encode = c; NSLog(@"c->compression_level:%d", c->compression_level); //}//acc //-------- //得到编码器后生成音频流 // 添加视频流 AVStream * stream = avformat_new_stream(rec->ff.out_fmt_mp4, NULL); //int audio_out_index = stream->index; //*avs = stream; rec->ff.out_audio_stream = stream; //--------------------------- // 设置音频流参数;对于封装来说,直接从编码器上下文拷贝即可 if (avcodec_parameters_from_context(stream->codecpar, aac_encode) < 0) { printf("copy en_code parameters fail"); //releaseSources(); return 0; } //-------- rec->ff.out_audio_encode = c; }//is_init_audio //if 初始化音频 //大步骤. 最后决定是否写入文件头 //这里其实有个问题:生成流的时候要同时知道音频帧和视频帧的信息,但这个时候可能只来了视频帧,甚至是来了几个视频帧后才音频帧 //所以实际上应该有队列缓冲,当音视频都到来后再一起生成 mp4 文件结构 //否则的话就是要先定死 mp4 中流的各个参数 //或者是以视频为准.丢弃音频包 if (0 == rec->ff.is_mp4file_create) { //物理文件是否已经生成.或者说是压缩包是否可以写入流了. rec->ff.is_mp4file_create = 1; //--------------------------- // 初始化封装器输出缓冲区 if (!(rec->ff.out_fmt_mp4->oformat->flags & AVFMT_NOFILE)) { if (avio_open2(&rec->ff.out_fmt_mp4->pb, rec->ff.filename_out, AVIO_FLAG_WRITE, NULL, NULL) < 0) { printf("avio_open2 fail"); //releaseSources(); return 0; } } // 写入封装器头文件信息;此函数内部会对封装器参数做进一步初始化 //-------- if (avformat_write_header(rec->ff.out_fmt_mp4, NULL) < 0) { //还没有生成流动就写入是不行的.所以步骤应该是: //1.生成 mp4 文件(对象)结构体 //2.生成编码器并打开 //3.生成音频或者视频流对象(结构体) //4.生成 mp4 文件缓冲 //5.写入 mp4 文件头 //6.写入各个压缩包 //7.写入 mp4 文件尾 printf("avformat_write_header fail"); //releaseSources(); return 0; } }// //--------------------------------------------------------------- //大步骤. 压缩 //void _doEncode(AVFrame *en_frame1) //for (int i=0; i<600; i++) //写入 60 帧空白图 int r = 0; //-------- // 编码前要设置好pts的值,如果en_ctx->time_base为{1,fps},那么这里pts的值即为帧的个数值 //不设置这个的话,第二帧编码时就会出错 // frame->pts = num_pts++; rec->ff.pts_frame_num_forAudio++; //frame->pts = rec->ff.pts_frame_num_forAudio++; //frame->pts = rec->ff.pts_frame_num_forAudio - 1; //要从 0 开始 //如何强制ffmpeg编码时输出一个关键帧 //在编码前设置//确实可以,不过这样会让文件很大,除非是要发送给对方,希望马上显示时//不影响苹果播放兼容性 ////blankFrame->pict_type = AV_PICTURE_TYPE_I; //FF_I_TYPE; ////blankFrame->key_frame=1; //---- //新建立一个 frame /* frame containing input raw audio */ AVFrame * new_frame = av_frame_alloc(); if (!new_frame) { fprintf(stderr, "Could not allocate audio frame\n"); exit(1); } new_frame->nb_samples = frame->nb_samples;//c->frame_size; new_frame->format = frame->format;//c->sample_fmt; new_frame->channel_layout = frame->channel_layout; /* allocate the data buffers */ ret = av_frame_get_buffer(new_frame, 0); if (ret < 0) { fprintf(stderr, "Could not allocate audio data buffers\n"); exit(1); } /* make sure the frame is writable -- makes a copy if the encoder * kept a reference internally */ ret = av_frame_make_writable(new_frame); if (ret < 0) exit(1); uint16_t * samples = (uint16_t*)new_frame->data[0]; //这一帧的二进制缓冲 //这里按道理要复制 samples 的数据,不过目前不知道准确的长度 //-------------------- //编码前的 pts 也是很重要的 NSLog(@"new_frame->pts: %lld, frame->pts: %lld", new_frame->pts, frame->pts); //frame->pts = new_frame->pts; //能解决 "总体码率" 极大的问题吗? 好象不行,由 ou_pkt->pts 控制 //frame->pts = -1; ////frame->pts = rec->ff.pts_frame_num_forAudio - 1; //应该就等于编码了的帧数 frame->pts = (rec->ff.pts_frame_num_forAudio - 1) * frame->nb_samples; //应该就等于编码了的帧数 //不对,实际上音频的数据是 帧数 * 帧数据长度.反正实际测试下来是如此 //也就是说 frame 的 pts 就是压缩后的 pts !!! ??? 这样似乎夸张了 /* //https://blog.csdn.net/dancing_night/article/details/45972361 音频pts 音频相对来说更难理解一些,因为音频的一个packet不止一帧,所以一秒到底有多少个packet就不知道,就别说如何计算pts了。 假设音频一秒有num_pkt个packet,那么这个num_pkt到底是多少? 这的从音频基础开始说起,我们知道音频有个采样率,就是一秒钟采用多少次,很多音频都是44100的采样率,也有8k的,那么这个采样率和num_pkt有什么关系呢? 我们发现在AVFrame中有一个比较重要的字段叫做nb_samples,这个字段名为采样数,此字段可以结合音频数据格式计算这个frame->data有多大,其实这个字段联合采样率还可以计算音频一秒有多少个packet。 计算公式如下: num_pkt = 采样率 / nb_samples; 这样我们就知道了音频每秒的包数目(可以见到理解为帧),有了此数据计算pts就和视频一模一样了, */ int num_pkt = rec->ff.out_audio_encode->bit_rate / frame->nb_samples; r = avcodec_send_frame(rec->ff.out_audio_encode, frame); //用自己生成的编码器 //r = avcodec_send_frame(rec->ff.out_audio_encode, new_frame); //av_frame_free(&new_frame); if (r != 0) { printf("编码失败:%d.", r); print_ff_error(r); } while (1) { AVPacket *ou_pkt = av_packet_alloc(); if (avcodec_receive_packet(rec->ff.out_audio_encode, ou_pkt) < 0) //换用自己的编码器 { av_packet_unref(ou_pkt); //奇怪,老是返回 AVERROR(EAGAIN); -- 是因为没有 pts //第一帧,未必能写入,据说这是表示需要更多的 avcodec_send_frame 加入图片 break; }//if // 成功编码了;写入之前要进行时间基的转换 //音频对不上 https://blog.csdn.net/dancing_night/article/details/45972361 //参数是输出编码器和输出流的 time_base (帧率)// // av_packet_rescale_ts(ou_pkt, rec->ff.out_audio_encode->time_base, // rec->ff.out_audio_stream->time_base); NSLog(@"rec->ff.out_audio_stream->time_base: %d, %d", rec->ff.out_audio_stream->time_base.num, rec->ff.out_audio_stream->time_base.den); //time_base: 1, 44100 //这指的是 1 秒 44100 (bit)换算成字节 = 5 512.5 即 5k 左右 //所以 pts 的单位就是 (44100 / 8) 个字节 NSLog(@"ou_pkt->size: %d", ou_pkt->size); //ou_pkt->pts = rec->ff.pts_frame_num_forAudio * rec->ff.out_audio_encode->bit_rate; //整个视频长了 10 倍左右 //ou_pkt->pts = rec->ff.pts_frame_num_forAudio * rec->ff.out_audio_stream->time_base.den; // ou_pkt->pts = av_rescale_q_rnd(ou_pkt->pts, rec->ff.out_audio_encode, rec->ff.out_audio_stream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX)); // ou_pkt->dts = av_rescale_q_rnd(ou_pkt->dts, rec->ff.out_audio_encode, rec->ff.out_audio_stream->time_base, ()(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX)); // ou_pkt->duration = av_rescale_q(ou_pkt->duration, in_stream->time_base, rec->ff.out_audio_stream->time_base); //ou_pkt->pos = -1; //ou_pkt->stream_index=0; //转换PTS/DTS(Convert PTS/DTS)//好多文章都说了这几个参数.不过它们的 ou_pkt->pts 中的所在包是从流中得到的 //所以得到的参数值应该是不对的. 而且它们都用的是 av_interleaved_write_frame NSLog(@"ou_pkt->pts: %lld", ou_pkt->pts); ou_pkt->pts = av_rescale_q_rnd(ou_pkt->pts, rec->ff.out_audio_encode->time_base, rec->ff.out_audio_stream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX)); ou_pkt->dts = av_rescale_q_rnd(ou_pkt->dts, rec->ff.out_audio_encode->time_base, rec->ff.out_audio_stream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX)); ou_pkt->duration = (int)av_rescale_q(ou_pkt->duration, rec->ff.out_audio_encode->time_base, rec->ff.out_audio_stream->time_base); ou_pkt->pos = -1; NSLog(@"ou_pkt->pts: %lld", ou_pkt->pts); NSLog(@"ou_pkt->dts: %lld", ou_pkt->dts); NSLog(@"ou_pkt->duration: %lld", ou_pkt->duration); NSLog(@"ou_pkt->dts: %lld", ou_pkt->dts); //感觉最后的值 pts(播放时间) 和 dts(解码时间) 是一样的,等于 当前帧数 * rec->ff.out_audio_encode->bit_rate; //因为原始包中的就是 当前帧数 * frame->nb_samples; //这两者唯一变化的就是压缩比率 ou_pkt->pts = (rec->ff.pts_frame_num_forAudio - 1) * frame->nb_samples; //应该就等于编码了的帧数 * 一帧的数据长度 ou_pkt->dts = ou_pkt->pts; //应该就等于编码了的帧数 * 一帧的数据长度 ou_pkt->duration = frame->pkt_duration; //其实等于 frame->nb_samples; //---- //奇怪,生成的文件并没有什么不同//https://www.cnblogs.com/vczf/p/13818609.html //转换成兼容格式,就是前面提到的什么 "h264_mp4toannexb" //据说 FLV/MP4/MKV等结构中,h264需要h264_mp4toannexb处理。添加SPS/PPS等信息。 //音频可以暂时先不过滤,而且要过滤也不是这样过的 // ret = av_bitstream_filter_filter(rec->ff.mH264bsfc, // rec->ff.out_audio_encode, //_this->mInputAvFmtCtx->streams[_this->mVIdx]->codec, // NULL, // &ou_pkt->data, // &ou_pkt->size, // ou_pkt->data, // ou_pkt->size, // 0); // // if (r != 0) // { // printf("编码过滤转换时失败:%d.", r); // print_ff_error(r); // } //---- ////ou_pkt.stream_index = 0; //不需要指定吗? //ou_pkt->dts = ou_pkt->pts; //据说这个也要正确 //av_write_frame(rec->ff.out_fmt_mp4, ou_pkt); //输出后不用释放? 由流自己释放吗? //这个和 av_interleaved_write_frame 有什么区别 av_interleaved_write_frame(rec->ff.out_fmt_mp4, ou_pkt);//写包 } av_frame_free(&new_frame); //--------------------------------------------------------------- //大步骤. 写入一帧 av_interleaved_write_frame //写入帧和压缩过程比较紧密,所以还是放在压缩过程里吧. //而且压缩时可能是要一次取几个包的.而写入帧其实只有一句话 av_write_frame() 所以实际上也不用独立出来 //--------------------------------------------------------------- //结束了. 据说一定要写入文件尾. //void FFRecordMP4::closeInputStream(void) { //void ff_record_close_input(void) //这只是关闭输入流 if(0) { //---- if (NULL != rec->ff.mInputAvFmtCtx) { // release resource avformat_close_input(&rec->ff.mInputAvFmtCtx); rec->ff.mInputAvFmtCtx = NULL; } if (NULL != rec->ff.mH264bsfc) { av_bitstream_filter_close(rec->ff.mH264bsfc); rec->ff.mH264bsfc = NULL; } if (NULL != rec->ff.mAACbsfc) { av_bitstream_filter_close(rec->ff.mAACbsfc); rec->ff.mAACbsfc = NULL; } }// //-------- //写入文件尾等 NSLog(@"rec->write_frame_count_audio: %d", rec->write_frame_count_audio); if (rec->write_frame_count_audio > 1010) //测试 100 帧先吧//音频的帧概念可能和视频还不太一样 { rec->stop = 1; //另外在各个示例中都有在关闭文件前写入一个空包的代码,称之为 flush encoder //http://www.manongjc.com/article/61294.html 处的代码也有 /* flush the encoder */ //encode(c, NULL, pkt, f); av_write_trailer(rec->ff.out_fmt_mp4); //写入文件尾 //释放各种资源 //ff_free_avframe_direct(blankFrame); if (rec->ff.out_fmt_mp4) { avformat_free_context(rec->ff.out_fmt_mp4); rec->ff.out_fmt_mp4 = NULL; } if (rec->ff.out_audio_encode) { avcodec_free_context(&rec->ff.out_audio_encode); rec->ff.out_audio_encode = NULL; } }// return 1; }//
clq
2021-02-23 12:57:46 发表
编辑
示例仅供参考,因为有好多依赖函数没空放出来.不过原理和关键参数已经给出了.
NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.