• Android下基于PCM的音频渲染


    环境准备

    请按照我之前的文章-Android下基于SDL的位图渲染,安装必要的开发环境。

    实践篇

    这里主要参考Beginning SDL 2.0(6) 音频渲染及wav播放,只不过将源从WAV文件改成PCM文件。

    准备你要播放的PCM文件

    如故你不想使用我提供的PCM,可以自己用ffmpeg转一个PCM文件,具体命令如下:

    $ ffmpeg -i src.wav -f s16le -acodec pcm_s16le out.pcm
    

    注意你需要知道这个PCM的采样率、量化位数、声道数。后续播放的时候会用到。
    生成之后,将pcm文件放到手机的目录下,我使用的是/sdcard/congtou_8k_mono_16bit.pcm

    SDL音频播放流程

    SDL中音频播放相对简单,只要通过SDL_OpenAudio打开设备,调用SDL_PauseAudio开始播放,播放结束调用SDL_CloseAudio。
    这里说明一点,SDL2支持多音频同时播放,不过在打开音频设备的时候需要调用SDL_OpenAudioDevice。
    下面是实现的代码:

    struct AudioPlayContext
    {
    	bool is_exit; // is audio play buffer empty?
    	FILE * fpcm;
    };
    
    void MyAudioCallback(void* userdata, Uint8* stream, int need_size)
    {
    	AudioPlayContext * context = reinterpret_cast<AudioPlayContext *>(userdata);
    	size_t actual_read = fread(stream, 1, need_size, context->fpcm);
    	if (0 == actual_read || feof(context->fpcm))
    	{
    	    LOGD("we meet stream end %u", actual_read);
    		context->is_exit = true;
    	}
    }
    
    extern "C" int pcm_main(int argc, char *argv[])
    {
        // init sdl
        if (0 != SDL_Init(SDL_INIT_AUDIO))
        {
            LOGE("%s %d SDL init audio failed", __FUNCTION__, __LINE__);
            return -1;
        }
    
        AudioPlayContext ctx;
        ctx.is_exit = false;
        ctx.fpcm = NULL;   
    
        // load yuv
        const char *pcm_path = "/sdcard/congtou_8k_mono_16bit.pcm";
        LOGI("natvie_SDL load pcm %s", pcm_path); 
        ctx.fpcm = fopen(pcm_path, "rb");
        if (NULL == ctx.fpcm)
        {
            LOGE("%s %d load pcm failed path %s", __FUNCTION__, __LINE__, pcm_path);
            SDL_Quit();
            return -2;
        }
    
        // open audio device
    	SDL_AudioSpec want, have;
        SDL_memset(&want, 0, sizeof(want)); /* or SDL_zero(want) */
        SDL_memset(&want, 0, sizeof(have)); /* or SDL_zero(want) */
    	want.freq = 8000;
    	want.format = AUDIO_S16LSB;
    	want.channels = 1;
    	want.samples = 1024;
    	want.callback = MyAudioCallback;  // you wrote this function elsewhere.
    	want.userdata = &ctx;
    
    	if (SDL_OpenAudio(&want, &have) < 0) 
    	{
    		LOGE("Failed to open audio: %s", SDL_GetError());
    		SDL_Quit();
    		return -3;
    	}	
    
    	if (have.format != want.format)
    	{	
    	    LOGD("We didn't get AUDIO_S16LSB audio format want %d have %d", want.format, have.format);
    	}	
    
    	// start audio playing.
    	SDL_PauseAudio(0);  
    
    	// wait for the end
    	while (!ctx.is_exit)
    	{
    		SDL_Delay(1000);
    	}
    
    	SDL_CloseAudio();
    	fclose(ctx.fpcm);    
    
        // Quit SDL
        SDL_Quit();	
    
        return 0;
    }
    

    音频播放的处理主要是按照音频设备回调函数填充需要播放的PCM的数据,只要数据填充正确基本就没有什么问题。

    理论篇

    如果你之前看过SDLActivity.java的实现代码,会发现其中有四个函数:

    public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames)
    public static void audioWriteShortBuffer(short[] buffer)
    public static void audioWriteByteBuffer(byte[] buffer)
    public static void audioQuit()
    

    还有一个变量,protected static AudioTrack mAudioTrack
    很明显的,SDL2通过JNI调用,将C层的PCM数据通过audioWriteShortBuffer/audioWriteByteBuffer调用到java层,并将PCM数据交给AudioTrack播放(至于AudioTrack怎么用,建议参考Android开发文档)。

    那么看一下SDL内部是怎么实现的?
    SDL_OpenAudioDevice --> open_audio_device --> AndroidAUD_CloseDevice --> Android_JNI_OpenAudioDevice
    最核心的函数是open_audio_device,在这个函数里会检查输入输出的音频参数(采样率、采样位数、声道数等),打开音频输出设备,同时启动音频输出线程,定期回调用户注册的回调函数,并将数据写入到音频设备中。有兴趣的建议看看SDL2的源码,关于音频的处理集中在SDL2-src/src/audio目录下。

    附加说明

    源码下载

    本文中涉及所有源码可以从我的git@OSC,下载之后需要切换到pcm_render的tag即可。

    其他

    本部分主要介绍如何在Android下使用SDL播放PCM数据,最终音频播放是通过AudioTrack实现的。

  • 相关阅读:
    nginx reload 与 restart 的区别
    求解一个数n的阶乘(递归求解 与 循环求解)
    类的加载机制
    JVM基础知识
    File类中常用的方法以及搜索路径下的包含指定词的文件
    给定10万数据,数据范围[0~1000),统计出现次数最多的10个数据,并打印出现次数
    TreeMap以及自定义排序的Comparable和Comparator的实现
    HashTable与LinkedHashMap
    HashMap
    Map接口
  • 原文地址:https://www.cnblogs.com/tocy/p/android-sdl-pcm-render.html
Copyright © 2020-2023  润新知