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;
    }
  • 相关阅读:
    Oracle函数如何把符串装换为小写的格式
    Oralce中的synonym同义词
    JS中getYear()的兼容问题
    How to do SSH Tunneling (Port Forwarding)
    所谓深度链接(Deep linking)
    upload size of asp.net
    发一个自动刷网站PV流量的小工具
    解决Visual Studio 2008 下,打开.dbml(LINQ) 文件时,提示"The operation could not be completed." 的问题。
    在资源管理器中使鼠标右键增加一个命令,运行cmd,同时使得当前路径为资源管理器当前的目录
    使用SQL语句获取Sql Server数据库的版本
  • 原文地址:https://www.cnblogs.com/renhui/p/10472990.html
Copyright © 2011-2022 走看看