美文网首页iOS 音视频
视频编解码基础篇3-FFmpeg转码实践

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

作者: doublej_yjj | 来源:发表于2019-03-14 10:49 被阅读17次

    音视频编解码:视频编解码基础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);
        }
    }
    

    待续...

    相关文章

      网友评论

        本文标题:视频编解码基础篇3-FFmpeg转码实践

        本文链接:https://www.haomeiwen.com/subject/blwtmqtx.html