截止到 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 渲染视频。