Skip to content

Latest commit

 

History

History
181 lines (138 loc) · 4.44 KB

0x06.md

File metadata and controls

181 lines (138 loc) · 4.44 KB

0x06 整理代码,封装解码器

截止到 0x05 教程,我们实现了多线程解码,缓存了 AVFrame,并且创建一个渲染的线程去模拟消耗。到这里代码已经有冗余了,是时候给播放器减轻下负担了,因此这一篇会抽取一个解码器出来,然后音视频解码逻辑使用该类完成。

思路分析

本篇教程的目的是抽取音视频均可使用的解码器,解码器初始化必要的参数:

@property (nonatomic, assign) AVFormatContext *ic;
@property (nonatomic, assign) int streamIdx;

有了这两个信息后,就能创建出来解码器上下文,获取到 AVStream 了,这是解码需要的。

解码器的输入是 AVPacket,输出是解码后的 AVFrame,为了减少解码器对播放器的依赖,这里使用 iOS 常用的代理设计模式解耦合。

@protocol FFDecoderDelegate0x06 <NSObject>

@required
///解码器向 delegater 要一个 AVPacket
- (int)decoder:(FFDecoder0x06 *)decoder wantAPacket:(AVPacket *)packet;
///将解码后的 AVFrame 给 delegater
- (void)decoder:(FFDecoder0x06 *)decoder reveivedAFrame:(AVFrame *)frame;

@end

核心代码

1、准备必要参数并打开解码器

- (FFDecoder0x06 *)openStreamComponent:(AVFormatContext *)ic streamIdx:(int)idx
{
    FFDecoder0x06 *decoder = [FFDecoder0x06 new];
    decoder.ic = ic;
    decoder.streamIdx = idx;
    if ([decoder open] == 0) {
        return decoder;
    } else {
        return nil;
    }
}

2、打开功能的具体实现

- (int)open
{
    if (self.ic == NULL) {
        return -1;
    }
    
    if (self.streamIdx < 0 || self.streamIdx >= self.ic->nb_streams){
        return -1;
    }
    
    AVStream *stream = self.ic->streams[self.streamIdx];
    
    //创建解码器上下文
    AVCodecContext *avctx = avcodec_alloc_context3(NULL);
    if (!avctx) {
        return AVERROR(ENOMEM);
    }
    
    //填充下相关参数
    if (avcodec_parameters_to_context(avctx, stream->codecpar)) {
        avcodec_free_context(&avctx);
        return -1;
    }
    
    av_codec_set_pkt_timebase(avctx, stream->time_base);
    
    //查找解码器
    AVCodec *codec = avcodec_find_decoder(avctx->codec_id);
    if (!codec){
        avcodec_free_context(&avctx);
        return -1;
    }
    
    avctx->codec_id = codec->id;
    
    //打开解码器
    if (avcodec_open2(avctx, codec, NULL)) {
        avcodec_free_context(&avctx);
        return -1;
    }
    
    stream->discard = AVDISCARD_DEFAULT;
    self.stream = stream;
    self.avctx = avctx;
    self.workThread = [[MRThread alloc] initWithTarget:self selector:@selector(workFunc) object:nil];
    
    return 0;
}

3、通过代理获取 AVPacket

...
//[阻塞等待]直到获取一个packet
int r = -1;
if ([self.delegate respondsToSelector:@selector(decoder:wantAPacket:)]) {
  r = [self.delegate decoder:self wantAPacket:&pkt];
}
...

4、通过代理发送解码后的 AVFrame

...
//正常解码
av_log(NULL, AV_LOG_VERBOSE, "decode a audio frame:%lld\n",frame->pts);
if ([self.delegate respondsToSelector:@selector(decoder:reveivedAFrame:)]) {
  [self.delegate decoder:self reveivedAFrame:frame];
}
...

5、释放内存

- (void)dealloc
{
    //释放解码器上下文
    if (_avctx) {
        avcodec_free_context(&_avctx);
        _avctx = NULL;
    }
}

6、播放器实现的代理方法

- (int)decoder:(FFDecoder0x06 *)decoder wantAPacket:(AVPacket *)pkt
{
    if (decoder == self.audioDecoder) {
        return packet_queue_get(&audioq, pkt, 1);
    } else if (decoder == self.videoDecoder) {
        return packet_queue_get(&videoq, pkt, 1);
    } else {
        return -1;
    }
}

- (void)decoder:(FFDecoder0x06 *)decoder reveivedAFrame:(AVFrame *)frame
{
    FrameQueue *fq = NULL;
    if (decoder == self.audioDecoder) {
        fq = &sampq;
    } else if (decoder == self.videoDecoder) {
        fq = &pictq;
    }
    
    if (fq != NULL) {
        Frame *af = NULL;
        if (NULL != (af = frame_queue_peek_writable(fq))) {
            av_frame_ref(af->frame, frame);
            frame_queue_push(fq);
        }
    }
}

总结

在抽取了解码器之后,播放器里的逻辑更加清晰了,冗余代码也不见了,解码器为播放器分担了解码的功能,播放器不需要去了解解码的实际过程,同时也为后续支持硬解打下基础。

下一篇将为大家介绍如何使用 UIImageView 渲染视频。