zoukankan      html  css  js  c++  java
  • SDL 开发实战(七): 使用 SDL 实现 PCM播放器

    在上文,我们做了YUV播放器,这样我们就入门了SDL播放视频。下面我们来做一个PCM播放,即使用SDL播放PCM数据。

    下面说明一下使用SDL播放PCM音频的基本流程,主要分为两大部分:初始化SDL、循环播放数据。

    1. 初始化SDL

    1). 初始化SDL

    执行的方法为SDL_Init(SDL_INIT_AUDIO)

    2). 打开音频设备

    使用SDL_OpenAudio()打开音频设备。该函数需要传入一个SDL_AudioSpec的结构体。

    这里SDL_OpenAudio() 函数的原型为:

    int SDL_OpenAudio(SDL_AudioSpec * desired, SDL_AudioSpec * obtained);

    它的参数是两个SDL_AudioSpec结构体,它们的含义:
    desired:期望的参数。
    obtained:实际音频设备的参数,一般情况下设置为NULL即可。

    其中SDL_AudioSpec结构体如下:

    typedef struct SDL_AudioSpec {
      int freq; /**< DSP frequency -- samples per second */
      SDL_AudioFormat format; /**< Audio data format */
      Uint8 channels; /**< Number of channels: 1 mono, 2 stereo */
      Uint8 silence; /**< Audio buffer silence value (calculated) */
      Uint16 samples; /**< Audio buffer size in samples (power of 2) */
      Uint16 padding; /**< Necessary for some compile environments */
      Uint32 size; /**< Audio buffer size in bytes (calculated) */
      SDL_AudioCallback callback;
      void *userdata;
    } SDL_AudioSpec;


    其中包含了关于音频各种参数:

    • freq:音频数据的采样率。常用的有48000,44100等。
    • format:音频数据的格式。举例几种格式:
    • AUDIO_U16SYS:Unsigned 16-bit samples
    • AUDIO_S16SYS:Signed 16-bit samples
    • AUDIO_S32SYS:32-bit integer samples
    • AUDIO_F32SYS:32-bit floating point samples
    • channels:声道数。例如单声道取值为1,立体声取值为2。
    • silence:设置静音的值。
    • samples:音频缓冲区中的采样个数,要求必须是2的n次方。
    • padding:考虑到兼容性的一个参数。
    • size:音频缓冲区的大小,以字节为单位。
    • callback:填充音频缓冲区的回调函数。
    • userdata:用户自定义的数据。

    在这里说明一下填充音频缓冲区的回调函数的作用。当音频设备需要更多数据的时候会调用该回调函数。

    回调函数的格式要求如下:

    void (SDLCALL * SDL_AudioCallback) (void *userdata, Uint8 * stream, int len);

    回调函数的参数含义如下:

    • userdata:SDL_AudioSpec结构中的用户自定义数据,一般情况下可以不用。
    • stream:该指针指向需要填充的音频缓冲区。
    • len:音频缓冲区的大小(以字节为单位)。

    在回调函数中可以使用SDL_MixAudio()完成混音等工作。注意:SDL2中必须首先使用SDL_memset()将stream中的数据设置为0。

    2. 循环播放数据

    1) 播放音频数据。

    使用SDL_PauseAudio()可以播放音频数据。SDL_PauseAudio() 函数的原型如下:

    void SDLCALL SDL_PauseAudio(int pause_on)

    当pause_on设置为0的时候即可开始播放音频数据。设置为1的时候,将会播放静音的值。

    2) 延时等待播放完成。

    使用像SDL_Delay()这样的延时函数即可。

    实战

    // SDL.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
    //
    
    #include "pch.h"
    #include <iostream>
    
    extern "C" {
    #include "SDL.h"
    }
    
    /**
     *
     * 使用SDL2播放PCM音频采样数据。SDL实际上是对底层绘图API(Direct3D,OpenGL)的封装,使用起来明显简单于直接调用底层API。
     *
     * 函数调用步骤如下:
     *
     * [初始化]
     * SDL_Init(): 初始化SDL。
     * SDL_OpenAudio(): 根据参数(存储于SDL_AudioSpec)打开音频设备。
     * SDL_PauseAudio(): 播放音频数据。
     *
     * [循环播放数据]
     * SDL_Delay(): 延时等待播放完成。
     * 
     * [播放音频的基本原则]
     * 声卡向你要数据而不是你主动推给声卡
     * 数据的多少是由音频参数决定的
     */
    
    //Buffer:
    //|-----------|-------------|
    //chunk-------pos---len-----|
    
    static  Uint8  *audio_chunk;
    static  Uint32  audio_len;
    static  Uint8  *audio_pos;
    
    void  fill_audio(void *udata, Uint8 *stream, int len) {
        //SDL 2.0
        SDL_memset(stream, 0, len);
        if (audio_len == 0)
            return;
        len = (len > audio_len ? audio_len : len);
    
        SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME);
        audio_pos += len;
        audio_len -= len;
    }
    
    int main(int argc, char* argv[])
    {
        //Init
        if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
            printf("Could not initialize SDL - %s
    ", SDL_GetError());
            return -1;
        }
        //SDL_AudioSpec
        SDL_AudioSpec wanted_spec;
        wanted_spec.freq = 48000;
        wanted_spec.format = AUDIO_S16SYS;
        wanted_spec.channels = 2;
        wanted_spec.silence = 0;
        wanted_spec.samples = 1024;
        wanted_spec.callback = fill_audio;
    
        if (SDL_OpenAudio(&wanted_spec, NULL) < 0) {
            printf("can't open audio.
    ");
            return -1;
        }
    
        FILE *fp = fopen("test.pcm", "rb+");
    
        if (fp == NULL) {
            printf("cannot open this file
    ");
            return -1;
        }
        int pcm_buffer_size = 4096;
        char *pcm_buffer = (char *)malloc(pcm_buffer_size);
        int data_count = 0;
    
        //Play
        SDL_PauseAudio(0);
    
        while (1) {
            if (fread(pcm_buffer, 1, pcm_buffer_size, fp) != pcm_buffer_size) {
                // Loop
                fseek(fp, 0, SEEK_SET);
                fread(pcm_buffer, 1, pcm_buffer_size, fp);
                data_count = 0;
            }
            printf("Now Playing %10d Bytes data.
    ", data_count);
            data_count += pcm_buffer_size;
            //Set audio buffer (PCM data)
            audio_chunk = (Uint8 *)pcm_buffer;
            //Audio buffer length
            audio_len = pcm_buffer_size;
            audio_pos = audio_chunk;
    
            while (audio_len > 0)//Wait until finish
                SDL_Delay(1);
        }
        free(pcm_buffer);
        SDL_Quit();
        return 0;
    }
  • 相关阅读:
    设计模式(十)——模板方法模式
    用imageROI来增加某范围的像素
    OpenCv练习
    字符串格式化(七)-format
    列表初始化
    设计模式(九)——适配器模式
    右值引用&&
    设计模式(八)——外观模式
    Confluence 6 企业环境或者网站托管的 Java 配置策略
    Confluence 6 管理应用服务器内存设置
  • 原文地址:https://www.cnblogs.com/renhui/p/10472990.html
Copyright © 2011-2022 走看看