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

待续...

你可能感兴趣的:(视频编解码基础篇3-FFmpeg转码实践)