音频处理(二) 音频输出

Windows下的音频输出常用的3中方法:

1. PlaySound:使用最简单直接,但是不够灵活,功能也非常单一,无法混音;

2. WaveOut:早期的Windows系统中广泛应用的音频输出程序接口,功能比PlaySound较完善(WaveIn用于音频输入);

3. DirectSound:现在Windows中主流的应用于音频输入输出的API,支持混音、独立音量控制、硬件加速、硬件仿真等强大的功能;

PlaySound

PlaySound的使用非常简单,下面是一个示例 (vs2013 Project):

#include "stdafx.h"
#include <Windows.h>
#include <mmsystem.h>

#pragma comment(lib, "winmm.lib")

const char fPath1[] = "C:/Windows/Media/Ring09.wav\0\0";

// 同步播放
void PlaySync()
{
    printf("Sync Start...\n");
    PlaySoundA((char*)fPath1, NULL, SND_FILENAME|SND_SYNC);
    printf("Sync Complete!\n");
}

// 异步播放
void PlayAsync()
{
    printf("Async Start...\n");
    PlaySoundA((char*)fPath1, NULL, SND_FILENAME|SND_ASYNC);
    printf("Async Complete!\n");
}

int main(int argc, char* argv[])
{
    PlaySync();
    //PlayAsync();

    Sleep(3000);
    return 0;
}

使用PlaySound方法之前须添加mmsystem.h和Windows.h两个头文件,并将winmm.lib链接库添加到工程,这里是用#pragma添加的,上面使用了同步和异步两种方式播放一段wav音频;

它们的区别是,同步方式下,PlaySound方法调用时会阻塞程序,直到这段音频播放结束,再返回往下继续执行;而异步方式下,调用PlaySound方法会立即返回,音频播放的同时,程序依然正常往下执行,这种情况下,上例如果没有Sleep方法休眠主线程,那么程序会直接结束,导致听不到完整的音乐。

另外,PlaySound无法同时播放两个音频,当重复调用PlaySound方法时,已经在播放的音频会中断,转而播放新的那段音频;又或者第二次调用PlaySound会失败返回FALSE,而已经播放的音频不受影响,这一切要看PlaySound的第三个参数的设定;比如上例中,如果重复调用,已经在播放的音频会中断,转而播放新的音频;而如果加上SND_NOSTOP,那么重复调用播放同一段音频时,之前的音频不会中断,而再次调用的PlaySound会失败返回FALSE;

SND_FILENAME表示采用文件名的方式加载音频资源,除此之外还有引用内存中已有音频资源的方式 (详见官方文档);

WaveOut

使用WaveOut进行音频输出大致分为几个步骤:创建缓冲区 — 读取音频文件 — 复制数据到缓冲区 — 播放,用一张图来说明音频播放的大致流程:

首先是创建缓存区,这里要用到官方定义的一个结构 WAVEHDR,这个结构是专用来进行音频数据缓存块和设备之间的桥梁,这里通过它可以提交音频数据给设备;

它的结构定义如下:

/* wave data block header */
typedef struct wavehdr_tag {
    LPSTR       lpData;                 /* 指向数据区起始地址 */
    DWORD       dwBufferLength;         /* 数据缓存大小(Byte) */
    DWORD       dwBytesRecorded;        /* used for input only */
    DWORD_PTR   dwUser;                 /* 开发者自由使用 */
    DWORD       dwFlags;                /* 标志位 */
    DWORD       dwLoops;                /* 循环计数器 */
    struct wavehdr_tag FAR *lpNext;     /* reserved for driver */
    DWORD_PTR   reserved;               /* reserved for driver */
} WAVEHDR, *PWAVEHDR, NEAR *NPWAVEHDR, FAR *LPWAVEHDR;

一般我们会创建3个以上的缓存块,循环地把数据写入、并按顺序提交给设备,那么设备就会按提交的顺序不断地播放已提交的那些缓存块音频数据,如上图;CPU控制硬盘读入数据,并复制到缓存区,然后按顺序提交一个个缓存块,播放完了的缓存块,可以继续读入再提交,循环往复;

之所以选择3个以上缓存块,是为了避免播放间隙时间的产生,微小的间隙在音频播放中,观感是难以忍受的;

创建代码:

#define BlockSize   1024*10    // 数据块缓存大小(这个案例中必须是 BufferSize 的整数倍)
#define BlockCount  12         // 数据块个数(不限,建议3个以上)

WAVEHDR* Blocks = NULL;

//
// 创建缓存区//
WAVEHDR* CreateBlocks()
{
    unsigned char* buffer;
    DWORD totalBufferSize = (BlockSize + sizeof(WAVEHDR))*BlockCount;

    // 申请的内存空间 = 块结构内存 + 缓存空间
    if ((buffer = (UCHAR*)malloc(totalBufferSize)) == NULL)
    {
        printf("Memory Malloc Error!\n");
        return NULL;
    }
    memset(buffer, 0, totalBufferSize);
    Blocks = (WAVEHDR*)buffer;
    buffer += sizeof(WAVEHDR) * BlockCount;
    for (int i = 0; i < BlockCount; i++)
    {
        Blocks[i].dwBufferLength = BlockSize;
        Blocks[i].lpData = (char*)buffer;
        buffer += BlockSize;
    }
    return Blocks;
}

缓存块创建完成,然后可以读入wav音频数据了,首先读入wav头结构,分析音频信息,头结构的分析直接引用上一篇中ReadHeader()方法;

//
// 读取Wav文件头,并验证文件格式
// 成功返回文件句柄,并重定位文件指针到数据区
// 失败返回NULL
//
HANDLE ReadHeader(char* path)
{
    HANDLE hFile = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        printf("Unable to Open File!");
        return NULL;
    }
    char buffer[512];
    DWORD readByte;
    if (ReadFile(hFile, buffer, sizeof(buffer), &readByte, NULL))
    {
        Riff_Header header;
        Fmt_Block fmt;
        Data_Block data;
        memcpy(&header, buffer, sizeof(Riff_Header));
        if (strncmp(header.szRiffId, "RIFF", 4) != 0) { CloseHandle(hFile); return NULL; }

        memcpy(&fmt, buffer + sizeof(Riff_Header), sizeof(Fmt_Block));
        if (strncmp(fmt.szFmtId, "fmt ", 4) != 0) { CloseHandle(hFile); return NULL; }

        memcpy(&data, buffer + sizeof(Riff_Header)+fmt.dwFmtSize+8, sizeof(Data_Block));
        if (strncmp(data.szDataId, "data", 4) != 0) { CloseHandle(hFile); return NULL; }

        memcpy(&wfx, &fmt.wFormatTag, sizeof(WAVEFORMATEX) - 2);
        wfx.cbSize = 0;

        // 重定位文件指针,到数据起始位置
        int headSize = sizeof(Riff_Header) + fmt.dwFmtSize + 8 + sizeof(Data_Block);
        headSize = (headSize / 8 + (headSize % 8 ? 1 : 0)) * 8;
        SetFilePointer(hFile, headSize, 0, FILE_BEGIN);

        return hFile;
    }
    return NULL;
}

ReadHeader

头文件分析完毕,可以得到波形文件信息wfx,现在可以打开设备,并读入音频数据到缓存块,最后提交,这样就可以播放音频了;

#define BufferSize    1024     // 读取文件的缓存大小

const char testWave[] = "C:/Windows/Media/Ring02.wav";

WAVEFORMATEX wfx;
CRITICAL_SECTION wcSection;static volatile int freeCount;      // 可用的缓存块数量,初始为BlockCount
static int curIndex;                // 当前要读入数据的缓存块的Index

int PlayWave(HANDLE hFile)
{
    unsigned char buffer[BufferSize];
    if (Blocks == NULL || hFile == NULL) return 1;

    freeCount = BlockCount;
    curIndex = 0;
    InitializeCriticalSection(&wcSection);

    HWAVEOUT hwo;    // 打开设备
    if (waveOutOpen(&hwo, WAVE_MAPPER, &wfx, (DWORD_PTR)WaveOutProc, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
    {
        printf("Unable to Open Mapper Device!");
        return 2;
    }
    WAVEHDR* current = &Blocks[curIndex];
    printf("Play Wave Start...\n");
    while (1)
    {
        DWORD readByte;
        if (!ReadFile(hFile, buffer, BufferSize, &readByte, NULL)) break;
        if (readByte == 0) break;
        if (readByte < BufferSize) memset(buffer + readByte, 0, BufferSize - readByte);

        memcpy(current->lpData + current->dwUser, buffer, BufferSize);
        current->dwUser += BufferSize;
        if (current->dwUser < BlockSize) continue;                // 必须先填满当前缓存块

        waveOutPrepareHeader(hwo, current, sizeof(WAVEHDR));      // 准备数据块
        waveOutWrite(hwo, current, sizeof(WAVEHDR));              // 把数据块提交给设备 (播放),立即返回

        EnterCriticalSection(&wcSection);
        freeCount--;
        LeaveCriticalSection(&wcSection);

        while (freeCount == 0) Sleep(10);          // 当所有的数据都准备好,且没有释放时,等待

        curIndex = (curIndex + 1) % BlockCount;
        current = &Blocks[curIndex];
        current->dwUser = 0;
    }
    while (freeCount < BlockCount) Sleep(100);    // 等待所有数据块播放完
    printf("Finish Play Wave!\n");

    DeleteCriticalSection(&wcSection);
    waveOutClose(hwo);

    return 0;
}
//
// 设备回调方法,三种情况下调用:
// 当设备开启、关闭、播放一个缓存块完成时
//
void CALLBACK WaveOutProc(HWAVEOUT hwo, UINT msg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
    if (msg != WOM_DONE) return;  // 过滤设备开启、关闭消息

    WAVEHDR* pwh = (WAVEHDR*)dwParam1;
    waveOutUnprepareHeader(hwo, pwh, sizeof(WAVEHDR));  // 释放播放完的块

    EnterCriticalSection(&wcSection);
    freeCount++;
    LeaveCriticalSection(&wcSection);
}

说明:

1. 上面的打开设备方法中,参数WaveOutProc是一个回调方法,就是设备的反馈消息,包括设备的打开、关闭、缓存块播放完成消息;

2. wcSection是一个用于数据同步的信息,通过wcSection中的访问计数,可以在多个线程同时访问EnterCriticalSection和LeaveCriticalSection之间的数据时,避免数据并发冲突;

如上例中,通过wcSection计数可以记录freeCount的访问个数,避免多线程(WaveOutProc方法)同时修改freeCount导致的错误;

那么,上面就是数据块的创建、读入数据、提交数据的过程,下面是调用它们:

#include "stdafx.h"#include <Windows.h>#include <mmsystem.h>#include "WavStruct.h"

#pragma comment(lib, "winmm.lib")

int main(int argc, char* argv[])
{
    HANDLE hFile = ReadHeader((char*)testWave);
    if (hFile == NULL) return 0;

    CreateBlocks();
    PlayWave(hFile);

    free(Blocks);
    CloseHandle(hFile);
    return 0;
}
时间: 2024-08-05 07:07:14

音频处理(二) 音频输出的相关文章

深度分析:Android4.3下MMS发送到附件为音频文件(音频为系统内置音频)的彩信给自己,添加音频-发送彩信-接收彩信-下载音频附件-预览-播放(二,发送彩信&lt;1&gt;)

当准备工作(添加附件,输入文本内容)完成之后,我们这里开始进行该流程分析的第二阶段,也就是发送彩信.这里我们从ComposeMessageActivity类的点击发送按钮(mSendButtonMms)的点击事件开始:<TAG 1-1> @Override public void onClick(View v) { if (mShowTwoButtons && (v == mSendButtonSmsViewSec || v == mSendButtonMmsViewSec)

iOS 9音频应用播放音频之ios9音频基本功能

iOS 9音频应用播放音频之ios9音频基本功能 在iOS 9音频应用开发中最为简单和常用的就是AVFoundation框架中的AVAudioPlayer类.虽然AVAudioPlayer类不能播放网络上的音频文件,但是它可以播放本地音频文件,以及缓冲区的文件.本章将讲解最为基础的音频播放--本地音频文件的播放. iOS 9音频应用开发基本功能 实现音频的播放需要使用到AVAudioPlayer类.AVAudioPlayer是AVFoundation.framework框架里面最基本的一个音频播

Android音频文件浏览+音频播放

该Demo运行后,会显示所有你sd卡上的音乐文件列表, 并可以点击列表选择某一首歌曲进行播放. 运行效果: 点击download出现: 然后点击歌曲调用系统播放器播放. 源代码: activity_audio_browser.xml: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tool

Java 获取amr音频格式的音频长度

import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; public class GetAmrDuration { /** * 得到amr的时长 * * @param file * @return * @throws IOException */ public static int getAmrDuration(File file) throws IOException { long du

iOS 9音频应用播放音频之控制播放速度

iOS 9音频应用播放音频之控制播放速度 iOS 9音频控制播放速度 iOS9音频文件在播放时是以一定的速度进行的.这个速度是可以进行更改的,从而实现iOS9音频文件的快速播放和慢速播放功能.要实现iOS9播放速度的更改需要使用AVAudioPlayer类中的rate属性实现.其语法形式如下: var rate: Float 其中,该属性设置的值为浮点类型,范围在0.5到2.0之间.如果该属性的值设置为1.0表示正常播放,它也是默认值.2.0表示以最快的速度进行播放,0.5表示以最慢的速度进行播

iOS 9音频应用播放音频之播放控制暂停停止前进后退的设置

iOS 9音频应用播放音频之播放控制暂停停止前进后退的设置 ios9音频应用播放控制 在“iOS 9音频应用播放音频之ios9音频基本功能”一文可以看到AVAudioPlayer类有很多的属性以及方法.本节将AVAudioPlayer类中常使用到的属性和方法进行详细的讲解. ios9音频应用暂停/停止 在音乐应用程序中都会有一个使音乐停止播放的按钮.当用户轻拍该按钮,正在播放的音乐就会停止.在iOS要想要正在播放的音频停止下来,可以使用AVAudioPlayer类中的pause()方法和stop

iOS 9音频应用播放音频之第一个ios9音频实例2

iOS 9音频应用播放音频之第一个ios9音频实例2 ios9音频应用关联 iOS9音频应用中对于在主视图上添加的视图或控件,在使用它们时必须要与插座变量进行关联.ios9插座变量其实就是为主视图中的视图或者控件起的别名,类似于实例化的对象.将主ios9视图中的Play Button按钮控件与插座变量playButton进行关联.具体的操作步骤如下: (1)使用设置编辑器的三个视图方式的图标,如图2.14所示,将Xcode的界面调整为如图2.15所示的效果. 图2.14  编辑器的三个视图方式的

使用MediaPlayer播放音频-----之二

MediaPlayer播放不同来源的音频文件: 一.播放应用的资源文件 1.调用MediaPlayer的create(Context  context , int  resid)方法加载指定资源文件. 2.调用MediaPlayer的start().pause().stop()等方法控制播放即可. 注:音频资源文件一般放在Android应用的/res/raw目录下. 二.播放应用的原始资源文件 1.调用Context的getAssets()方法获取应用的AssetManager. 2.调用Ass

iOS音频播放(二):AudioSession

(本文转自码农人生) 前言 在实施前一篇中所述的7个步骤步之前还必须面对一个麻烦的问题,AudioSession. AudioSession简介 AudioSession这个玩意的主要功能包括以下几点(图片来自官方文档): 1. 确定你的app如何使用音频(是播放?还是录音?) 2. 为你的app选择合适的输入输出设备(比如输入用的麦克风,输出是耳机.手机功放或者airplay) 3. 协调你的app的音频播放和系统以及其他app行为(例如有电话时需要打断,电话结束时需要恢复,按下静音按钮时是否