ijkplayer音频解码播放架构分析

2024-06-15 10:38

本文主要是介绍ijkplayer音频解码播放架构分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

ijkplayer是一款跨平台播放器,支持Android与iOS播放,音频解码默认使用FFmpeg的avcodec软解。Android端播放音频可以用OpenSL ES和AudioTrack,而iOS端播放音频默认使用AudioQueue。

一、iOS音频解码播放

采用pipeline形式创建音频播放组件,整体流水线如下:

1、创建IjkMediaPlayer

首先调用ijkplayer_ios.m创建IjkMediaPlayer,具体代码如下:

IjkMediaPlayer *ijkmp_ios_create(int (*msg_loop)(void*))
{IjkMediaPlayer *mp = ijkmp_create(msg_loop);if (!mp)goto fail;mp->ffplayer->vout = SDL_VoutIos_CreateForGLES2();if (!mp->ffplayer->vout)goto fail;mp->ffplayer->pipeline = ffpipeline_create_from_ios(mp->ffplayer);if (!mp->ffplayer->pipeline)goto fail;return mp;
fail:ijkmp_dec_ref_p(&mp);return NULL;
}

2、创建pipeline

在第一步调用ffpipeline_ios.c创建pipeline,对func_open_video_decoder和func_open_audio_output函数指针进行赋值:

IJKFF_Pipeline *ffpipeline_create_from_ios(FFPlayer *ffp)
{IJKFF_Pipeline *pipeline = ffpipeline_alloc(&g_pipeline_class, sizeof(IJKFF_Pipeline_Opaque));......pipeline->func_open_video_decoder = func_open_video_decoder;pipeline->func_open_audio_output  = func_open_audio_output;return pipeline;
}static SDL_Aout *func_open_audio_output(IJKFF_Pipeline *pipeline, FFPlayer *ffp)
{return SDL_AoutIos_CreateForAudioUnit();
}

3、创建SDL_Aout

在ff_play.c的ffp_prepare_async_l()方法调用ff_ffpipeline.c的ffpipeline_open_audio_output()方法:

int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)
{......if (!ffp->aout) {ffp->aout = ffpipeline_open_audio_output(ffp->pipeline, ffp);if (!ffp->aout)return -1;}
}

然后调用ffpipeline_ios.c的func_open_audio_output()方法,最终调用ijksdl_aout_ios_audiounit.m创建SDL_Aout,分别为open_audio、pause_audio、flush_audio、close_audio函数指针进行赋值:

SDL_Aout *SDL_AoutIos_CreateForAudioUnit()
{SDL_Aout *aout = SDL_Aout_CreateInternal(sizeof(SDL_Aout_Opaque));......aout->open_audio  = aout_open_audio;aout->pause_audio = aout_pause_audio;aout->flush_audio = aout_flush_audio;aout->close_audio = aout_close_audio;......return aout;
}

4、打开音频解码器

在ff_ffplay.c的stream_component_open()方法寻找并且打开音频解码器:

static int stream_component_open(FFPlayer *ffp, int stream_index)
{// 寻找解码器codec = avcodec_find_decoder(avctx->codec_id);// 打开解码器if ((ret = avcodec_open2(avctx, codec, &opts)) < 0) {goto fail;}......switch (avctx->codec_type) {case AVMEDIA_TYPE_AUDIO:// 打开音频输出设备if ((ret = audio_open(ffp, channel_layout, nb_channels, sample_rate, &is->audio_tgt)) < 0)goto fail;// 启动音频解码线程if ((ret = decoder_start(&is->auddec, audio_thread, ffp, "ff_audio_dec")) < 0)goto out;......default:break;}goto out;
fail:avcodec_free_context(&avctx);
out:av_dict_free(&opts);return ret;
}

5、创建AudioQueue

在audio_open()方法里调用ijksdl_aout.c的SDL_AoutOpenAudio()方法打开音频输出设备,并且设置pcm数据回调函数:

static int audio_open(FFPlayer *opaque, int64_t wanted_channel_layout, int wanted_nb_channels, int wanted_sample_rate, struct AudioParams *audio_hw_params)
{// 设置sdl_audio_callback作为callback函数wanted_spec.callback = sdl_audio_callback;// 打开音频输出设备while (SDL_AoutOpenAudio(ffp->aout, &wanted_spec, &spec) < 0) {if (is->abort_request)return -1;}
}

ijksdl_aout.c内部调用ijksdl_aout_ios_audiounit.m的aout_open_audio(),最终调用IJKSDLAudioQueueController.m创建AudioQueue用于播放音频:

- (id)initWithAudioSpec:(const SDL_AudioSpec *)aSpec
{// 创建AudioQueue,设置回调函数OSStatus status = AudioQueueNewOutput(&streamDescription,IJKSDLAudioQueueOuptutCallback,(__bridge void *) self,NULL,kCFRunLoopCommonModes,0,&audioQueueRef);// 启动AudioQueuestatus = AudioQueueStart(audioQueueRef, NULL);return self;
}

AudioQueue内部会创建一个工作线程进行播放,如下图所示:

6、解码音频

音频解码线程:

static int audio_thread(void *arg)
{......do {// decode audioif ((got_frame = decoder_decode_frame(ffp, &is->auddec, frame, NULL)) < 0)goto the_end;// filter audio#if CONFIG_AVFILTERif ((ret = av_buffersrc_add_frame(is->in_audio_filter, frame)) < 0)goto the_end;while ((ret = av_buffersink_get_frame_flags(is->out_audio_filter, frame, 0)) >= 0) {tb = av_buffersink_get_time_base(is->out_audio_filter);}#endifframe_queue_push(&is->sampq);} while (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF);the_end:av_frame_free(&frame);return ret;
}

sdl_audio_callback的代码如下:

static void sdl_audio_callback(void *opaque, Uint8 *stream, int len)
{......while (len > 0) {if (is->audio_buf_index >= is->audio_buf_size) {// decode and convert frameaudio_size = audio_decode_frame(ffp);}if (!is->muted && is->audio_buf && is->audio_volume == SDL_MIX_MAXVOLUME)memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);else {memset(stream, 0, len1);if (!is->muted && is->audio_buf)SDL_MixAudio(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1, is->audio_volume);}len -= len1;stream += len1;is->audio_buf_index += len1;}
}

然后内部调用audio_decode_frame()进行音频转换:

static int audio_decode_frame(FFPlayer *ffp)
{......do {// 获取下一个frameframe_queue_next(&is->sampq);} while (af->serial != is->audioq.serial);// 音频转换swr_convert(is->swr_ctx, out, out_count, in, af->frame->nb_samples);return resampled_data_size;
}

7、播放音频

在AudioQueue的回调函数会定时回调,然后调用ff_ffplay.c的sdl_audio_callback()获取pcm数据来播放:

static void IJKSDLAudioQueueOuptutCallback(void * inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {@autoreleasepool {// 获取pcm数据(*aqController.spec.callback)(aqController.spec.userdata, inBuffer->mAudioData, inBuffer->mAudioDataByteSize);// pcm数据入队列AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);}
}

这篇关于ijkplayer音频解码播放架构分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1063216

相关文章

python panda库从基础到高级操作分析

《pythonpanda库从基础到高级操作分析》本文介绍了Pandas库的核心功能,包括处理结构化数据的Series和DataFrame数据结构,数据读取、清洗、分组聚合、合并、时间序列分析及大数据... 目录1. Pandas 概述2. 基本操作:数据读取与查看3. 索引操作:精准定位数据4. Group

MySQL中EXISTS与IN用法使用与对比分析

《MySQL中EXISTS与IN用法使用与对比分析》在MySQL中,EXISTS和IN都用于子查询中根据另一个查询的结果来过滤主查询的记录,本文将基于工作原理、效率和应用场景进行全面对比... 目录一、基本用法详解1. IN 运算符2. EXISTS 运算符二、EXISTS 与 IN 的选择策略三、性能对比

MySQL 内存使用率常用分析语句

《MySQL内存使用率常用分析语句》用户整理了MySQL内存占用过高的分析方法,涵盖操作系统层确认及数据库层bufferpool、内存模块差值、线程状态、performance_schema性能数据... 目录一、 OS层二、 DB层1. 全局情况2. 内存占js用详情最近连续遇到mysql内存占用过高导致

深度解析Nginx日志分析与499状态码问题解决

《深度解析Nginx日志分析与499状态码问题解决》在Web服务器运维和性能优化过程中,Nginx日志是排查问题的重要依据,本文将围绕Nginx日志分析、499状态码的成因、排查方法及解决方案展开讨论... 目录前言1. Nginx日志基础1.1 Nginx日志存放位置1.2 Nginx日志格式2. 499

Olingo分析和实践之EDM 辅助序列化器详解(最佳实践)

《Olingo分析和实践之EDM辅助序列化器详解(最佳实践)》EDM辅助序列化器是ApacheOlingoOData框架中无需完整EDM模型的智能序列化工具,通过运行时类型推断实现灵活数据转换,适用... 目录概念与定义什么是 EDM 辅助序列化器?核心概念设计目标核心特点1. EDM 信息可选2. 智能类

Olingo分析和实践之OData框架核心组件初始化(关键步骤)

《Olingo分析和实践之OData框架核心组件初始化(关键步骤)》ODataSpringBootService通过初始化OData实例和服务元数据,构建框架核心能力与数据模型结构,实现序列化、URI... 目录概述第一步:OData实例创建1.1 OData.newInstance() 详细分析1.1.1

Olingo分析和实践之ODataImpl详细分析(重要方法详解)

《Olingo分析和实践之ODataImpl详细分析(重要方法详解)》ODataImpl.java是ApacheOlingoOData框架的核心工厂类,负责创建序列化器、反序列化器和处理器等组件,... 目录概述主要职责类结构与继承关系核心功能分析1. 序列化器管理2. 反序列化器管理3. 处理器管理重要方

SpringBoot中六种批量更新Mysql的方式效率对比分析

《SpringBoot中六种批量更新Mysql的方式效率对比分析》文章比较了MySQL大数据量批量更新的多种方法,指出REPLACEINTO和ONDUPLICATEKEY效率最高但存在数据风险,MyB... 目录效率比较测试结构数据库初始化测试数据批量修改方案第一种 for第二种 case when第三种

解决1093 - You can‘t specify target table报错问题及原因分析

《解决1093-Youcan‘tspecifytargettable报错问题及原因分析》MySQL1093错误因UPDATE/DELETE语句的FROM子句直接引用目标表或嵌套子查询导致,... 目录报js错原因分析具体原因解决办法方法一:使用临时表方法二:使用JOIN方法三:使用EXISTS示例总结报错原

MySQL中的LENGTH()函数用法详解与实例分析

《MySQL中的LENGTH()函数用法详解与实例分析》MySQLLENGTH()函数用于计算字符串的字节长度,区别于CHAR_LENGTH()的字符长度,适用于多字节字符集(如UTF-8)的数据验证... 目录1. LENGTH()函数的基本语法2. LENGTH()函数的返回值2.1 示例1:计算字符串