视频编解码基础篇3-FFmpeg转码实践

音视频编解码:视频编解码基础1-FFmpeg结构与API摘要
音视频编解码:视频编解码基础2-FFmpeg解码实践
音视频编解码:视频编解码基础篇3-FFmpeg转码实践
音视频编解码:视频编解码基础篇4-FFmpeg解码播放

FFmpeg转码流程

1.注册所有编解码器
2.根据URL打开媒体文件
3.检索视频流信息
4.根据视频流编解码上下文获取编码格式
5.根据编码格式打开编码器
6.读取音视频数据 packet
7.获得两路流视频流解码成 YUV
8.YUV->CVPixelBufferRef->CMSampleBufferRef
9.通过AVAssetWriter和预定的封装格式转储封装预定格式视频.
    AVFormatContext *_inFormatCtx;
    SwsContext *_videoSwsContext;
    AVCodecContext *_videoDecodecCtx;
    AVCodecParameters *_videoDecodecParameters;
    AVSampleFormat _destSampleFmt;
    void *_swrBuffer;
    NSUInteger _swrBufferSize;
    AVFrame *_destVideoFrame;
    AVFrame *_videoFrame;
    AVRational _videoFPSTimebase;
    AVRational _audioFPSTimebase;
    NSLock * _lock;   
    BOOL _closeStream;     
    BOOL _errorOccur;
    BOOL _isTransCoding;    
    int _destWidth;
    int _destHeight;
    unsigned char* _yuv420Buffer;
- (BOOL)openInput:(NSString *)urlString{
//组册组件
     av_register_all();
     int result = -1;
     frameNum = 0;
    do {
        _inFormatCtx = avformat_alloc_context();
        if (!_inFormatCtx)  break;
        //打开封装格式->打开文件
        result = avformat_open_input(&_inFormatCtx, [urlString cStringUsingEncoding:NSUTF8StringEncoding], NULL, NULL);
        if (result < 0) break;
        //查找流信息
        result = avformat_find_stream_info(_inFormatCtx, NULL);
        if (result < 0) break;
        //检索对应的音视频流
        for (int i = 0; i < _inFormatCtx->nb_streams; i++) {
            AVStream *stream = _inFormatCtx->streams[i];
            AVCodecParameters* codecPara = stream->codecpar;;
            if (AVMEDIA_TYPE_VIDEO == codecPara->codec_type) {
                result = [self openVideoStream:stream];
                if (result < 0) break;
            } else if (AVMEDIA_TYPE_AUDIO == codecPara->codec_type){
                暂不对音频做处理
               // result = [self openAudioStream:stream];
                //if (result < 0) break;
            }
        }
        
        AVCodec *dec;
        // 选择,查找视频流
        result = av_find_best_stream(_inFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &dec, 0);
        if (result < 0) {
            printf( "找不到视频输入信息\n");
            return result;
        }
        video_stream_index = result;
        pCodecCtx = _inFormatCtx->streams[video_stream_index]->codec;
        if (result < 0) break;
      // 打印视频流信息
        av_dump_format(_inFormatCtx, 0, [inputURL.lastPathComponent cStringUsingEncoding: NSUTF8StringEncoding], false);
} while (0);
    
    return result >= 0;
}

-(int)openVideoStream:(AVStream*)videoStream{
    
    AVCodecParameters* codecParam = videoStream->codecpar;
    AVCodecContext* content = NULL;
    int result = -1;
    
    do {
       //查找解码器
        AVCodec* codec = avcodec_find_decoder(codecParam->codec_id);
        if (!codec) {
            printf("\n 没有找到解码器:%s \n", avcodec_get_name(codecParam->codec_id));
            break;
        }
        // 配置解码器
        content = avcodec_alloc_context3(codec);
        if (!content) break;
        result = avcodec_parameters_to_context(content, codecParam);
        if (result < 0) break;
        // 打开解码器
        result = avcodec_open2(content, codec, NULL);
        if (result < 0) break;
        
        _videoFrame             = av_frame_alloc();
        _videoDecodecCtx        = content;
        _videoDecodecParameters = codecParam;
        
        _destWidth  = MIN(_destWidth, codecParam->width);
        _destHeight =  _destWidth * codecParam->height / codecParam->width / 4 * 4;
        
        if (videoStream->avg_frame_rate.den && videoStream->avg_frame_rate.num) {
            _videoFPSTimebase = videoStream->avg_frame_rate;
        } else if (videoStream->r_frame_rate.den && videoStream->r_frame_rate.num){
            _videoFPSTimebase = videoStream->r_frame_rate;
        } else {
            _videoFPSTimebase = av_make_q(25, 1);
        }
        if (_destWidth != _videoDecodecCtx->width || _destHeight != _videoDecodecCtx->height || AV_PIX_FMT_YUV420P != _videoDecodecCtx->pix_fmt) {
/*
sws_getContext()
sws_scale()
sws_freeContext()
设置sws_getContext配置获取SwsContext结构体,包括视频制式YUV420P,大小,格式
*/
    [_lock lock];
    [self closeSwsCtx];
    _destVideoFrame     = alloc_image(AV_PIX_FMT_YUV420P, _destWidth, _destHeight);
    _videoSwsContext    = sws_getContext(_videoDecodecParameters->width, _videoDecodecParameters->height, (AVPixelFormat)_videoDecodecParameters->format,
                                         _destWidth, _destHeight, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
    [_lock unlock];
        }
    } while (0);
    
    if (result < 0) {
        if (content) {
            avcodec_free_context(&content);
            content = NULL;
        }
    }
    
    return result;
}

/*alloc_image()此自定义函数的功能是按照指定的宽、高、像素格式来分析图像内存。
核心函数为
int av_image_alloc(uint8_t *pointers[4], int linesizes[4],int w, int h, enum AVPixelFormat pix_fmt, int align);
int av_image_get_buffer_size(enum AVPixelFormat pix_fmt, int width, int height, int align);
在此配置都可,相关选择和异同将在后续API对比中详细说明
*/
AVFrame* alloc_image(AVPixelFormat pix_fmt, int width, int height)
{
    AVFrame *frame ;
    uint8_t *frame_buf;
    int size;
    
    frame           = av_frame_alloc();
    frame->width    = width;
    frame->height   = height;
    if (!frame) return NULL;
    size        = av_image_get_buffer_size(pix_fmt, width, height, 1);
    frame_buf   = (uint8_t *)av_malloc(size);
    if (!frame_buf){
        av_frame_free(&frame);
        return NULL;
    }
    av_image_fill_arrays(frame->data, frame->linesize, frame_buf, pix_fmt, width, height, 1);
    return frame;
}
//读取视频帧线程
-(void)startTranscoding{
    [NSThread detachNewThreadSelector:@selector(readFramesThread) toTarget:self withObject:nil];
}
//子线程读取视频帧
-(void)readFramesThread{
    
    @autoreleasepool {
       // 数据包初始化
        AVPacket pkt;
        av_init_packet(&pkt);
        CGFloat completePercent     = 0;
        CGFloat prePercent  = 0;
       NSLog (@"开始转码进度:duration = %f ", [self duration]);
        while (!_closeStream && !_errorOccur) {
            [_lock lock];
            if (_closeStream) {
                [_lock unlock];
                break;
            }
            int ret = av_read_frame(_inFormatCtx, &pkt);
            if (ret < 0) {
                if (AVERROR_EOF == ret) {
                    completePercent = 1.0;
                }
               NSLog@("读取视频错误error-->, %s", av_err2str(ret))
                [_lock unlock];
                break;
            }
            int streamIndex = pkt.stream_index;
            AVStream* in_stream  = _inFormatCtx->streams[streamIndex];
            if (AVMEDIA_TYPE_VIDEO == in_stream->codecpar->codec_type) {
                completePercent = [self getCompletePercent:&pkt];
                if (completePercent >= prePercent + 0.01) {//避免频繁的回调
                    prePercent = completePercent;
                    //回调转码进度 
                }
                [self decodeVideo:&pkt];
            } else if(AVMEDIA_TYPE_AUDIO == in_stream->codecpar->codec_type){
                //[self decodeAudio:&pkt];
            } else {
  /*  AVMEDIA_TYPE_UNKNOWN = -1,  ///< Usually treated as  
    AVMEDIA_TYPE_DATA
    AVMEDIA_TYPE_VIDEO,
    AVMEDIA_TYPE_AUDIO,
    AVMEDIA_TYPE_DATA,          ///< Opaque data information usually continuous
    AVMEDIA_TYPE_SUBTITLE,
    AVMEDIA_TYPE_ATTACHMENT,    ///< Opaque data information usually sparse
    AVMEDIA_TYPE_NB*/
            }
            av_packet_unref(&pkt);
            [_lock unlock];
            [NSThread sleepForTimeInterval:0.005];//避免此线程cup占用过高
        }
        if (!_closeStream) {
            //如果 _closeStream = true, 说明是外部主动close的,转码结束.
            [self.assetWriter stopRecord:^(BOOL isSucced) {
                if (isSucced && completePercent > .90) {
                   //文件转码结束,回调主程转储存文件savePath
                } else {
                    //转储存文件出错
                }
                _isTransCoding = false;
            }];
        } else if (_errorOccur) {
            //转码结束出错
        }else{
            //文件转码结束,回调主程转储存文件savePath
        }
      NSLog(@"文件转码结束,completePercent = %f", completePercent);
    }
}

待续...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270