前言

本文基于ffmpeg7.0版本,实现了在iOS平台利用ffmpeg视频解析与显示视频功能。


一、ffmpeg打开视频文件,获取流信息

打开视频文件,如果是远程文件需要调用avformat_network_init方法

- (BOOL)openFile:(NSString *)path
           error:(NSError **)error{
    if(!path){
        NSLog(@"路径为空");
        return NO;
    }
    _isNetwork = isNetWorkPath(path);
    if(self.isNetwork){
        avformat_network_init();
    }
    _path = path;
    MovieError errCode = [self openInput: path];
    if (errCode == MovieErrorNone){
        MovieError videoErr = [self openVideoStream];
        if (videoErr != MovieErrorNone){
            errCode = videoErr;
        }
    }
    
    if (errCode != MovieErrorNone){
        [self closeFile];
        if (error){
            *error = [NSError errorWithDomain:@"video" code:errCode userInfo:nil];
        }
        return NO;
    }
    
    return MovieErrorNone;
}

创建AVFormatContext对象,获取视频流信息

- (MovieError) openInput: (NSString *) path
{
    AVFormatContext *formatCtx = NULL;
    
    if (_interruptCallback) {
        formatCtx = avformat_alloc_context();
        if (!formatCtx) return MovieErrorOpenFile;
        AVIOInterruptCB cb = {interrupt_callback, (__bridge void *)(self)};
        formatCtx->interrupt_callback = cb;
    }
    
    if (avformat_open_input(&formatCtx, [path cStringUsingEncoding: NSUTF8StringEncoding], NULL, NULL) < 0) {
        if (formatCtx) avformat_free_context(formatCtx);
        return MovieErrorOpenFile;
    }
    
    if (avformat_find_stream_info(formatCtx, NULL) < 0) {
        avformat_close_input(&formatCtx);
        return MovieErrorStreamInfoNotFound;
    }

    av_dump_format(formatCtx, 0, [path.lastPathComponent cStringUsingEncoding: NSUTF8StringEncoding], false);
    
    _formatContext = formatCtx;
    return MovieErrorNone;
}

二、查找视频流

AVFormatContext对象中取得AVStream对象及解码器对象,通过avcodec_parameters_to_context函数将AVCodecParameters参数对象设置给AVCodecContext对象,avcodec_open2打开视频流。

- (MovieError)openVideoStream: (NSInteger)videoStream{
    AVCodecParameters *codecpar = _formatContext->streams[videoStream]->codecpar;
    const AVCodec *codec = avcodec_find_decoder(codecpar->codec_id);
    AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
    avcodec_parameters_to_context(codec_ctx, codecpar);
    AVCodecContext *codecCtx = codec_ctx;
    
    if (!codec) return MovieErrorCodecNotFound;
    if (avcodec_open2(codecCtx, codec, NULL) < 0) return MovieErrorOpenCodec;
    _videoFrame = av_frame_alloc();
    if (!_videoFrame) {
        avcodec_close(codecCtx);
        return MovieErrorAllocateFrame;
    }
    _videoStream = videoStream;
    _videoCodecCtx = codecCtx;
    
    AVStream *st = _formatContext->streams[_videoStream];
    avStreamFPSTimeBase(st, 0.04, &_fps, &_videoTimeBase);
    
    return MovieErrorNone;
}

三、视频显示

解析视频帧

- (void) asyncDecodeFrames{
    if (self.decoding) return;
    self.decoding = YES;
    __weak ViewController *weakSelf = self;
    __weak JVideoDecoder *weakDecoder = _decoder;
    const CGFloat duration = _decoder.isNetwork ? .0f : 0.1f;
    dispatch_async(_dispatchQueue, ^{
        __strong ViewController *strongSelf = weakSelf;
        if (!strongSelf.playing) return;
        BOOL good = YES;
        while (good)
        {
            good = NO;
            @autoreleasepool {
                __strong JVideoDecoder *decoder = weakDecoder;
                if (decoder && (decoder.validVideo)) {
                    NSArray *frames = [decoder decodeFrames:duration];
                    if (frames.count) {
                        __strong ViewController *strongSelf = weakSelf;
                        if (strongSelf)
                            good = [strongSelf addFrames:frames];
                    }
                }
            }
            __strong ViewController *strongSelf = weakSelf;
            if (strongSelf) strongSelf.decoding = NO;
        }
    });
}

- (NSArray *)decodeFrames:(CGFloat)minDuration{
    if (_videoStream == -1) return nil;
    NSMutableArray *result = [NSMutableArray array];
    CGFloat decodedDuration = 0;
    BOOL finished = NO;
    while (!finished){
        AVPacket *packet = av_packet_alloc();
        if (av_read_frame(_formatContext, packet) < 0) {
            _isEOF = YES;
            break;
        }
        
        if (packet->stream_index ==_videoStream) {
            int pktSize = packet->size;
            if (pktSize > 0) {
                if (avcodec_send_packet(_videoCodecCtx, packet) == 0){
                    while (avcodec_receive_frame(_videoCodecCtx, _videoFrame) == 0){
                        VideoFrame *frame = [self handleVideoFrame];
                        if (frame) {
                            [result addObject:frame];
                            _position = frame.position;
                            decodedDuration += frame.duration;
                            if (decodedDuration > minDuration)
                                finished = YES;
                        }
                    }
                }
            }
        }
    }
    return result;
}

通过RGB格式图片显示视频

- (CGFloat) presentFrame{
    CGFloat interval = 0;
    if (_decoder.validVideo){
        VideoFrame *frame;
        @synchronized(_videoFrames) {
            
            if (_videoFrames.count > 0) {
                
                frame = _videoFrames[0];
                [_videoFrames removeObjectAtIndex:0];
                _bufferedDuration -= frame.duration;
            }
        }
        
        if (frame)
            interval = [self presentVideoFrame:frame];
    }
    
    return interval;
}

- (CGFloat) presentVideoFrame:(VideoFrame *)frame{
    _moviePosition = frame.position;
    VideoFrameRGB *rgbFrame = (VideoFrameRGB *)frame;
    _imageView.image = [rgbFrame asImage];
    return frame.duration;
}

四.代码

demo地址


五、总结

以上就是今天对ffmpeg的一些总结。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐