登录 用户中心() [退出] 后台管理 注册
   
您的位置: 首页 >> CLQ工作室开源代码 - [函数库] >> 主题: ffmpeg 中录制的视频 iphone6s 能否播放的关键     [回主站]     [分站链接]
标题
ffmpeg 中录制的视频 iphone6s 能否播放的关键
clq
浏览(323) + 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 发表 编辑

示例仅供参考,因为有好多依赖函数没空放出来.不过原理和关键参数已经给出了.


总数:2 页次:1/1 首页 尾页  
总数:2 页次:1/1 首页 尾页  


所在合集/目录
ffmpeg 更多



发表评论:
文本/html模式切换 插入图片 文本/html模式切换


附件:



NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.

Copyright © 2005-2020 clq, All Rights Reserved
版权所有
桂ICP备15002303号-1