【PortAudio】PortAudio オーディオ処理ライブラリ デモ

オーディオ処理関連の概念

https://zhuanlan.zhihu.com/p/91837880

音の三要素

  • ラウドネス: 音の振動の振幅に関連します
  • ピッチ: 主に周波数に関係しており、音波の周波数が高いほど、ピッチも高くなります。
  • 音色:音の出る素材によって異なり、同じ高さ、大きさでも素材が異なれば発せられる音も異なります。

デジタルオーディオ

サンプリング周波数: 自然界で連続する音声信号をサンプリングし、ナイキスト定理に従って時間軸上の信号をデジタル化します。つまり、一定の時間間隔Δ t \Delta tに従って信号をデジタル化します。Δt は、アナログ信号の瞬時値をポイントごとにサンプリングします。KaTeX 解析エラー: 未定義の制御シーケンス: \x 位置 1: \̲x̲(t)サンプリング周波数が高いほど、音の復元度が高く、品質が向上し、占有スペースも大きくなります。

量子化: アナログ信号の連続振幅を一定の間隔で限られた数の離散値に変換します。ビット深度: サンプリングされたデータを記述するために使用されるバイナリ ビットの数を示します (通常は 16 ビット)。
コーディング

符号化: 一定の規則に従って、量子化された値を 2 進数で表し、2 値または多値のデジタル信号ストリームに変換します。上記のデジタル化プロセスはパルス符号変調とも呼ばれ、通常、オーディオの生データ形式はパルス符号変調 (PCM) データと呼ばれます。PCM データを記述するには、いくつかの定量化指標が必要です。一般的に使用される定量化指標には、サンプリング レート、ビット深度、バイト順序、チャネル数などがあります。

チャンネル番号: 現在の PCM ファイルに含まれるチャンネルの数 (モノラル チャンネルかデュアル チャンネルか)

Endianness : オーディオ PCM データ ストレージのバイト オーダーがビッグ エンディアンであるかリトル エンディアンであるかを示します。データ処理効率を高めるために、通常はリトル エンディアン ストレージです。

オーディオエンコーディング

CD の音質を例にとると、量子化形式は 2Byte、サンプリング周波数は 44100、サウンド チャンネルは 2 です。この情報は CD の音質を表します。CD のビット レートは 44100 * 16 * 2 = 13783125 kbps となり、1 分間に 10.09MB のスペースを占有します。

圧縮アルゴリズムには、非可逆圧縮と可逆圧縮が含まれます。

  • MP3、MPEG-1 または MPEG-2 Audio Layer III は、かつて非常に普及していたデジタル オーディオ エンコードおよび非可逆圧縮形式で、オーディオ データの量を大幅に削減するように設計されています。
  • AAC (Advanced Audiocoding) は、フラウンホーファー IIS、ドルビー ラボラトリーズ、AT&T、ソニーなどの企業が共同開発し、1997 年に発表された MPEG-2 に基づくオーディオ コーディング テクノロジです。AACはMP3よりも圧縮率が高く、同じサイズの音声ファイルであればAACの方が高音質です。
  • WMA (Windows Media Audio) は、Microsoft が開発したデジタル オーディオ圧縮形式であり、それ自体に非可逆圧縮形式と可逆圧縮形式が含まれています。

1 はじめに

PortAudio は、無料のクロスプラットフォームのオープンソース オーディオ I/O ライブラリです。I/Oというとファイルを思い浮かべるかもしれませんが、PortAudioが操作するI/Oはファイルではなくオーディオデバイスです。C/C++ オーディオ プログラムの設計と実装を簡素化し、Windows、Macintosh OS X、UNIX (Linux のさまざまなバージョンは言うまでもありません) 上で実行できます。PortAudio を使用すると、さまざまなプラットフォーム上のアプリケーションを移行できます。たとえば、PortAudio ベースのアプリケーションの Android バージョンを開発できます。

PortAudio の API は非常にシンプルで、単純なコールバック関数またはブロッキング読み取り/書き込みインターフェイスを通じてサウンドを録音または再生します。PortAudio には、正弦波オーディオ信号の再生、オーディオ入力の処理、オーディオの録音と再生、オーディオ デバイスのリストなど、多数のサンプル プログラムが付属しています。

この記事では、録音と再生の例を使用して、PortAudio の使用プロセスを示します。完全なコードは次のとおりです。

#include <stdio.h>
#include <stdlib.h>
#include <memory>
#include "portaudio.h"
using namespace std;
// 回调函数定义
static int audioCallback(const void* inputBuffer, void* outputBuffer,
    unsigned long framesPerBuffer,
    const PaStreamCallbackTimeInfo* timeInfo,
    PaStreamCallbackFlags statusFlags,
    void* userData)
{
    
    
    // 在此处处理音频数据
    // 将输入缓冲区中的数据复制到输出缓冲区以回放声音
    if (inputBuffer != NULL)
        memcpy(outputBuffer, inputBuffer, framesPerBuffer * sizeof(float));

    return paContinue;
}

int main()
{
    
    
    PaStream* stream;
    PaError err;

    // 初始化PortAudio库
    err = Pa_Initialize();
    if (err != paNoError) {
    
    
        printf("初始化PortAudio失败: %s\n", Pa_GetErrorText(err));
        return 1;
    }

    // 打开默认的音频输入和输出设备
    err = Pa_OpenDefaultStream(&stream,
        1,      // 输入通道数
        1,      // 输出通道数
        paFloat32,  // 采样格式
        44100,  // 采样率
        256,    // 缓冲区大小(每个缓冲区的帧数)
        audioCallback,  // 回调函数
        NULL);  // 用户数据

    if (err != paNoError) {
    
    
        printf("打开音频流失败: %s\n", Pa_GetErrorText(err));
        return 1;
    }

    // 启动音频流
    err = Pa_StartStream(stream);
    if (err != paNoError) {
    
    
        printf("启动音频流失败: %s\n", Pa_GetErrorText(err));
        return 1;
    }

    printf("录音已开始,请按 Enter 键停止...\n");
    getchar();

    // 停止音频流
    err = Pa_StopStream(stream);
    if (err != paNoError) {
    
    
        printf("停止音频流失败: %s\n", Pa_GetErrorText(err));
        return 1;
    }

    // 关闭音频流和PortAudio库
    err = Pa_CloseStream(stream);
    if (err != paNoError) {
    
    
        printf("关闭音频流失败: %s\n", Pa_GetErrorText(err));
        return 1;
    }

    Pa_Terminate();
    printf("录音已停止。\n");

    return 0;
}

2. ダウンロードしてインストールします

2.1 ソースコードのコンパイル

ソース コードのダウンロード パスは http://www.portaudio.com/download.html です。VS
でコンパイルする手順については、http://portaudio.com/docs/v19-doxydocs/compile_windows.html を参照してください
。 ubuntu の場合は、apt-get install portaudio19 を直接使用します。

2.2 Vcpkg のインストール

vcpkg C++ パッケージ マネージャーを使用してインストールする

 vcpkg install portaudio:x64-windows 

3. 利用の流れ

PortAudio アプリケーションを作成するには、コールバック関数をマスターするだけで済みます。

  1. オーディオ処理中に PortAudio が自動的に呼び出すコールバック関数を作成します。
  2. PA ライブラリを初期化し、I/O 用のストリームを開きます。
  3. ストリームを開始すると、PA が舞台裏でコールバック関数を呼び出します
  4. コールバック関数では、inputBuffer から音声データを読み取ったり、outputBuffer に音声データを書き込んだりできます。
  5. コールバック関数は 1 を返すか、対応する関数を呼び出してフローを停止します
  6. ストリームを閉じてから PA を終了します

コールバック関数に加えて、PA はブロッキング I/O モデルもサポートしていますが、すべての機能がサポートされているわけではありません。したがって、コールバック関数を使用することをお勧めします。

フローチャート
ここに画像の説明を挿入します

3.1 コールバック関数の書き込み

まずはPAのヘッダファイルを紹介します

#include "portaudio.h"

コールバック関数は、PA がオーディオ データを取得するときと、PA が出力としてオーディオ データを必要とするときの 2 つの状況で呼び出されます。

プログラム内の他のコードとは異なり、一部のシステムはコールバックを特別なスレッドで処理したり、割り込みを介して処理したりするため、コールバックは魔法のような場所です。音声を時間通りにスピーカーに届けたい場合は、コールバック関数がすぐに実行できるようにする必要があります。プラットフォームが異なると、どの操作が安全でどの操作が安全でないかも異なります。一般的なガイドラインは、メモリの割り当てと解放の操作、ファイルの読み取りと書き込み、printf、またはコンテキストの切り替えを引き起こす可能性のある操作を含む、一定時間内に返せないその他の OS に依存する操作を実行しないことです。

コールバック関数のプロトタイプ:

typedef int PaStreamCallback( 
	const void *input,
	void *output,
	unsigned long frameCount, 
	const PaStreamCallbackTimeInfo* timeInfo, 
	PaStreamCallbackFlags statusFlags, 
	void *userData );

たとえば、記録されたデータを再生したい場合は、このコールバックを次のように記述できます。

// 回调函数定义
static int audioCallback(
	const void* inputBuffer, 
	void* outputBuffer,
    unsigned long framesPerBuffer,
    const PaStreamCallbackTimeInfo* timeInfo,
    PaStreamCallbackFlags statusFlags,
    void* userData)
{
    
    
    // 在此处处理音频数据
    // 将输入缓冲区中的数据复制到输出缓冲区以回放声音
    if (inputBuffer != NULL)
        memcpy(outputBuffer, inputBuffer, framesPerBuffer * sizeof(float));

    return paContinue;
}

音声データを処理したい場合は次のように記述できます

typedef struct
{
    
    
    float left_phase;
    float right_phase;
}   
paTestData;
/* This routine will be called by the PortAudio engine when audio is needed.
 * It may called at interrupt level on some machines so don't do anything
 * that could mess up the system like calling malloc() or free().
*/ 
static int patestCallback( 
	const void  			*inputBuffer, 
	void 					*outputBuffer,
    unsigned long 			framesPerBuffer,
    const 					PaStreamCallbackTimeInfo* timeInfo,
    PaStreamCallbackFlags 	statusFlags,
    void 					*userData )
{
    
    
    /* Cast data passed through stream to our structure. */
    paTestData *data = (paTestData*)userData; 
    float *out = (float*)outputBuffer;
    unsigned int i;
    (void) inputBuffer; /* Prevent unused variable warning. */
    
    for( i=0; i<framesPerBuffer; i++ )
    {
    
    
         out++ = data->left_phase;  /* left */
         out++ = data->right_phase;  /* right */
        /* Generate simple sawtooth phaser that ranges between -1.0 and 1.0. */
        data->left_phase += 0.01f;
        /* When signal reaches top, drop back down. */
        if( data->left_phase >= 1.0f ) data->left_phase -= 2.0f;
        /* higher pitch so we can distinguish left and right. */
        data->right_phase += 0.03f;
        if( data->right_phase >= 1.0f ) data->right_phase -= 2.0f;
    }
    return 0;
}

3.2 PortAudio の初期化

Pa_Initialize() を呼び出すと、利用可能なデバイスのスキャンがトリガーされ、後でクエリを実行できます。ほとんどの PA 関数と同様に、paError 情報が返されますが、paNoError でない場合は、エラーが発生したことを意味します。

auto err = Pa_Terminate();
if( err != paNoError )
   printf(  "PortAudio error: %s\n", Pa_GetErrorText( err ) );

3.3 デフォルトを使用したスト​​リームの操作

このステップでは、ファイルと同様にストリームが開きます。入出力したい音声、チャンネル数、データ形式、サンプルレートなどを指定できます。「デフォルト」ストリームを開くということは、デフォルトの入出力デバイスを開くことを意味し、デバイスのリストを取得してリストから 1 つを選択する手間が省けます。(これを行う方法については後で説明します。)

#define SAMPLE_RATE (44100)
static paTestData data;
.....
    PaStream *stream;
    PaError err;
    /* Open an audio I/O stream. */
    err = Pa_OpenDefaultStream( &stream,
                                0,          /* no input channels */
                                2,          /* stereo output */
                                paFloat32,  /* 32 bit floating point output */
                                SAMPLE_RATE,
                                256,        /* frames per buffer, i.e. the number
                                                   of sample frames that PortAudio will
                                                   request from the callback. Many apps
                                                   may want to use
                                                   paFramesPerBufferUnspecified, which
                                                   tells PortAudio to pick the best,
                                                   possibly changing, buffer size.*/
                                patestCallback, /* this is your callback function */
                                &data ); /*This is a pointer that will be passed to
                                                   your callback*/
    if( err != paNoError ) goto error;

ここでのデータは、コールバックの userData パラメータに対応します。

上の例は、再生要件を満たすように書き込まれたストリームを示しています。また、読み取りや録音のためにストリームを開いたり、読み取りと書き込みを同時に行って同時録音と再生、さらにはリアルタイムのオーディオ処理を行うことも可能です。再生と録音を同時に行う場合は、有効な入力パラメータと出力パラメータを持つストリームを 1 つだけ開きます。
たとえば、記事の冒頭で述べた録音や再生の初期化などです。

err = Pa_OpenDefaultStream(&stream,
        1,      // 输入通道数
        1,      // 输出通道数
        paFloat32,  // 采样格式
        44100,  // 采样率
        256,    // 缓冲区大小(每个缓冲区的帧数)
        audioCallback,  // 回调函数
        NULL);  // 用户数据

    if (err != paNoError) {
    
    
        printf("打开音频流失败: %s\n", Pa_GetErrorText(err));
        return 1;
    }

注記:

  • 一部のプラットフォーム上のデバイスは読み取り専用または書き込み専用である場合があります
  • 複数のストリームをオンにすることはできますが、同期するのは困難です
  • 一部のプラットフォーム デバイスは複数のストリームを開くことをサポートしていません
  • 複数のストリームの使用は、他の機能ほど徹底的にテストされていない可能性があります。
  • PortAudio ライブラリへの呼び出しは、同じスレッドから行われるか、ユーザーによって同期される必要があります。

3.4 ストリームの開始、停止、中止

ストリームを開始すると、PortAudio はオーディオの再生を開始します。Pa_StartStream()が呼び出されると、PortAudio はオーディオ処理を実行するために Callback 関数の呼び出しを開始します。

err = Pa_StartStream( stream );
if( err != paNoError ) goto error;

コールバック関数との通信には、オープン コールで渡されるデータ構造、グローバル変数、または他のプロセス間通信手法を使用できますが、フォアグラウンド プロセスが割り込みを最も予期しないときにコールバック関数が呼び出される可能性があることに注意してください。したがって、破損しやすい二重リンク リストのような複雑なデータ構造の共有は避け、ミューテックスなどのロックの使用も避けてください。これにより、コールバック関数がブロックされて音声が失われる可能性があります。これらの手法は、一部のプラットフォームでデッドロックを引き起こす可能性もあります。

PortAudio は、ストリームが停止されるまで、コールバック関数の呼び出しと音声の処理を続けます。これはさまざまな方法で実行できますが、これを実行する前に、音声の一部が数秒間のスリープで処理されることを確認したいと思います。これは、Pa_Sleep() を使用して簡単に実現できます。patests/ ディレクトリ内のサンプルの多くは、この目的でこれを使用しています。さまざまな理由により、正確なスケジュール設定をこの関数に依存することはできないため、ストリームが期待どおりの時間実行されない可能性がありますが、この例ではこれで十分であることに注意してください。

/* Sleep for several seconds. */
Pa_Sleep(NUM_SECONDS*1000);

さあ、プレイをやめなければなりません。これを行うにはいくつかの方法がありますが、最も簡単な方法は Pa_StopStream() 関数を呼び出すことです。

err = Pa_StopStream( stream );
if( err != paNoError ) goto error;

Pa_StopStream() 関数は、コールバック関数で処理するバッファが確実に再生されるように設計されており、これにより遅延が発生する可能性があります。あるいは、Pa_AbortStream() 関数を呼び出すこともできます。一部のプラットフォームでは、プロセスを中止する方が高速であり、コールバック関数によって処理されたデータの一部が再生されなくなる可能性があります。

フローを停止する別の方法は、コールバック関数から paComplete または paAbort を返すことです。paComplete は最後のバッファが再生されることを保証しますが、paAbort はストリームをできるだけ早く停止します。この手法を使用してプロセスを停止する場合は、プロセスを再開する前に Pa_StopStream() 関数を呼び出す必要があります。

3.5 ストリームの終了と PortAudio の終了

プロセスが終了したら、プロセスを閉じてリソースを解放する必要があります。

err = Pa_CloseStream( stream );
if( err != paNoError ) goto error;

これについては PortAudio の初期化時にすでに説明しましたが、忘れた場合に備えて、完了したら必ず PortAudio を終了してください。

err = Pa_Terminate( );
if( err != paNoError ) goto error;

4. 参考文献

https://blog.csdn.net/GG_SiMiDa/article/details/77185755
http://files.portaudio.com/docs/v19-doxydocs/terminated_portaudio.html

おすすめ

転載: blog.csdn.net/qq_30340349/article/details/131509624