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

相关文章

Java 中编码与解码的具体实现方法

《Java中编码与解码的具体实现方法》在Java中,字符编码与解码是处理数据的重要组成部分,正确的编码和解码可以确保字符数据在存储、传输、读取时不会出现乱码,本文将详细介绍Java中字符编码与解码的... 目录Java 中编码与解码的实现详解1. 什么是字符编码与解码?1.1 字符编码(Encoding)1

Android 缓存日志Logcat导出与分析最佳实践

《Android缓存日志Logcat导出与分析最佳实践》本文全面介绍AndroidLogcat缓存日志的导出与分析方法,涵盖按进程、缓冲区类型及日志级别过滤,自动化工具使用,常见问题解决方案和最佳实... 目录android 缓存日志(Logcat)导出与分析全攻略为什么要导出缓存日志?按需过滤导出1. 按

Linux中的HTTPS协议原理分析

《Linux中的HTTPS协议原理分析》文章解释了HTTPS的必要性:HTTP明文传输易被篡改和劫持,HTTPS通过非对称加密协商对称密钥、CA证书认证和混合加密机制,有效防范中间人攻击,保障通信安全... 目录一、什么是加密和解密?二、为什么需要加密?三、常见的加密方式3.1 对称加密3.2非对称加密四、

MySQL中读写分离方案对比分析与选型建议

《MySQL中读写分离方案对比分析与选型建议》MySQL读写分离是提升数据库可用性和性能的常见手段,本文将围绕现实生产环境中常见的几种读写分离模式进行系统对比,希望对大家有所帮助... 目录一、问题背景介绍二、多种解决方案对比2.1 原生mysql主从复制2.2 Proxy层中间件:ProxySQL2.3

python使用Akshare与Streamlit实现股票估值分析教程(图文代码)

《python使用Akshare与Streamlit实现股票估值分析教程(图文代码)》入职测试中的一道题,要求:从Akshare下载某一个股票近十年的财务报表包括,资产负债表,利润表,现金流量表,保存... 目录一、前言二、核心知识点梳理1、Akshare数据获取2、Pandas数据处理3、Matplotl

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. 智能类