iOS オーディオのリアルタイム録音と再生

要件: 最近、会社は建物のインターホン機能を実行する必要があります: 屋外ステーション (WIFI に接続されている) がダイヤルして屋内ユニット (WIFI に対応) を呼び出し、屋内ユニットは呼び出しを受信した後、UDP ブロードキャストを介して受信データを転送します。 、携帯電話(対応するWIFIに接続されている)がビデオストリームを受信した後、リアルタイムでビデオデータを表示します(携帯電話が応答した後、電話を切ることができます、携帯電話が応答した後、室内ユニットはビデオを表示しません、しかしそれを転送するだけです。)

簡単に言えば、携帯電話のクライアントは、リアルタイムでビデオを表示し、受信した音声データをリアルタイムで再生し、携帯電話のマイクで受信した音声を送信できる、ライブ放送プラットフォームに似たソフトウェアを作成する必要があります。電話はリアルタイムで室内ユニットに戻ります. 室内ユニットはドアステーションへの転送を担当します. 

この記事では、iOSが受信した音声データをリアルタイムで録音および再生する方法を紹介します 

システムのフレームワークを使用してサウンドと録音データをリアルタイムで再生する場合は、Audio Queue サービスを知る必要があります。

AudioToolbox フレームワークのオーディオ キュー サービスは、オーディオの再生と録音を完全に行うことができます。

オーディオ サービス キューは、次の 3 つの部分で構成されます。

1. 3 つのバッファー バッファー: 各バッファーは、オーディオ データを格納するための一時的な倉庫です。
2. バッファ キュー バッファ キュー: オーディオ バッファを含む順序付けられたキュー。
3. コールバック CallBack: カスタム キュー コールバック関数。

 仕組みは百度次第!

私の簡単な理解:

再生の場合: システムは再生のためにバッファ キューから各バッファ内のデータを自動的に取り出します. 必要なことは、受信したデータを循環的にバッファに入れ、残りをシステムに任せて実装することです. .

録音の場合: システムは録音されたサウンドをキュー内の各バッファに自動的に入れます。コールバック関数からデータを独自のデータに変換するだけでOKです。

#pragma mark -- リアルタイムで再生

1. システム フレームワーク AudioToolbox.framework AVFoundation.framework をインポートします。

2. マイクのパーミッションを取得し、プロジェクトの Info.plist ファイルに Privacy - Microphone Usage Description を追加します. Description: アプリはマイクにアクセスしようとしています.

3. サウンドを再生するクラス EYAudio を作成する 

EYAudio.h

#import <Foundation/Foundation.h> 

@interface EYAudio : NSObject 

// 再生されたデータ ストリーム data 
- (void)playWithData:(NSData *)data; 
// 音声再生に問題がある場合はリセットできます
- (void) resetPlay ; 
// 再生を停止
- (void)stop; 

@end

EYAudio.m

 #import "EYAudio.h"

#import <AVFoundation/AVFoundation.h> 
#import <AudioToolbox/AudioToolbox.h> 

#define MIN_SIZE_PER_FRAME 1920 //各パッケージのサイズ、室内ユニットの要件は 960 です。詳細については、以下の構成情報を参照してください
#define QUEUE_BUFFER_SIZE 3 //バッファ番号
#define SAMPLE_RATE 16000 //サンプリング周波数

@interface EYAudio(){ 
  AudioQueueRef audioQueue; //オーディオ再生キュー
  AudioStreamBasicDescription _audioDescription; 
  AudioQueueBufferRef audioQueueBuffers[QUEUE_BUFFER_SIZE]; //オーディオキャッシュ
  BOOL audioQueueBufferSIUUE は audioBuffer が Queue[UFF Use 
  NSLock *sysnLock; 
  NSMutableData *tempData; 
  OSStatus osState; 
} 
@end 

@implementation EYAudio 

#pragma mark - AVAudioSessionCategoryMultiRoute の再生と録音を事前に設定
+ (void)initialize 
  if (audioQueue != nil) { 
{
  NSError *error = nil; 
  //再生のみしたい: AVAudioSessionCategoryPlayback 
  //記録のみしたい: AVAudioSessionCategoryRecord 
  //「再生と記録」を同時にしたい に設定する必要があります: AVAudioSessionCategoryPlayAndRecord の代わりに AVAudioSessionCategoryMultiRoute (設定は簡単ではありません) ) 
  BOOL ret = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryMultiRoute error:&error]; 
  if (!ret) { 
    NSLog(@"サウンド環境の設定に失敗しました"); 
    return; 
  } 
  //オーディオセッションを有効にする
  ret = [[AVAudioSession sharedInstance ] setActive:YES エラー :&error]; 
  if (!ret) 
  { 
    NSLog(@"Failed to start"); 
    return; 
  } 
} 

- (void)resetPlay 
{ 
}
    AudioQueueReset(audioQueue); 
  } 

- (void)stop 
{ 
  if (audioQueue != nil) { 
    AudioQueueStop(audioQueue,true); 
  } 

  audioQueue = nil; 
  sysnLock = nil; 
} 

- (instancetype)init 
{ 
  self = [super init]; 
  if (self) { sysnLock = [[NSLock alloc 
    ] 
    init]; 
    //オーディオ パラメータの特定の情報を設定するには、
    バック
    グラウンド

    に問い合わせる必要がありますchannel_audioDescription.mChannelsPerFrame 
    = 1; 
    //各パケットはデータ、各パケットの下のフレーム数を検出します。つまり、各データ パケットに含まれるフレームの数
    _audioDescription.mFramesPerPacket = 1; 
    //各サンプル ポイント 16 ビットの量子化された音声は、サンプル ポイントごとにビットを占有します
    _audioDescription.mBitsPerChannel = 16;
    _audioDescription.mBytesPerFrame = (_audioDescription.mBitsPerChannel / 8) * _audioDescription.mChannelsPerFrame; 
    // パケットあたりの合計バイト数、フレームあたりのバイト数 * パケットあたりのフレーム数
    _audioDescription.mBytesPerPacket = _audioDescription.mBytesPerFrametPacketmDescriptes ; 

    // プレーヤーの内部を使用新しい出力を再生するスレッド
    AudioQueueNewOutput(&_audioDescription, AudioPlayerAQInputCallback, (__bridge void * _Nullable)(self), nil, 0, 0, &audioQueue); // 音量を設定します AudioQueueSetParameter(audioQueue, 

    kAudioQueueParam_Volume 
    , 1.0); 
    }
 
    // 必要なバッファを初期化します
    for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) { 
      audioQueueBufferUsed[i] = false; 
      osState = AudioQueueAllocateBuffer(audioQueue, MIN_SIZE_PER_FRAME, &audioQueueBuffers[i]); 

    osState = AudioQueueStart(audioQueue, NULL); 
    if (osState != noErr) { 
      NSLog(@"AudioQueueStart エラー"); 
    自己を返し
  ます
} 

// 播放数据
-(void)playWithData:(NSData *)data 
{ 
  [sysnLock ロック]; 

  tempData = [NSMutableData new]; 
  [tempData appendData: データ]; 
  NSUInteger len = tempData.length; 
  バイト *バイト = (バイト*)malloc(len); 
  [tempData getBytes:バイト長: len]; 

  int i = 0;
  while (true) { 
    if (!audioQueueBufferUsed[i]) { 
      audioQueueBufferUsed[i] = true; 
      壊す; 
    } その他 { 
      i++; 
      if (i >= QUEUE_BUFFER_SIZE) { 
        i = 0; 
      } 
    } 
  } 

  audioQueueBuffers[i] -> mAudioDataByteSize = (unsigned int)len; 
  // put mAudioDataには、バイトの先頭アドレスから始まるlenバイトがi番目のバッファに与えられます
  memcpy(audioQueueBuffers[i] -> mAudioData, bytes, len); 

  // オブジェクトを解放します
  free(bytes); 

  // put the i 番目のバッファ キューに入れ、残りはシステムに渡されます
  AudioQueueEnqueueBuffer(audioQueue, audioQueueBuffers[i], 0, NULL); 

  [sysnLock unlock]; 
} 

// ******** ******** ************ コールバック**************************** ******* 
// コールバック バッファの状態を未使用に設定 
static void AudioPlayerAQInputCallback(void* inUserData,AudioQueueRef audioQueueRef, AudioQueueBufferRef audioQueueBufferRef) { 

  EYAudio* audio = (__bridge EYAudio *)inUserData;

  [オーディオ resetBufferState:audioQueueRef and:audioQueueBufferRef]; 
} 

- (VOID) Resetbufferstate: (AudioQueueref) AudioQueueref and: (Audioqueuebufferref) Audioqueuebufferref { 
  // 防止 保護 让 Audioqueue 后续 不不 了了 安全 了 安全 f f f f f f { 
  { 
    audioqueebufferref-> maudiodatabytabytabytABYTABYTABYTABYTABYTABYTABYTABY ; 
    Byte* byte = audioQueueBufferRef->mAudioData; 
    バイト = 0; 
    AudioQueueEnqueueBuffer(audioQueueRef, audioQueueBufferRef, 0, NULL); 
  } 

  for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) { 
    // このバッファを未使用として設定
    if (audioQueueBufferRef == audioQueueBuffers[i]) { 
      audioQueueBufferUsed[i] = false; 
    } 
  } 
} @

終了

 外部使用: 常に次のメソッドを呼び出して NSData を渡します

- (void)playWithData:(NSData *)データ;

#pragma mark -- リアルタイム記録

1. システム フレームワーク AudioToolbox.framework AVFoundation.framework をインポートします。

2. レコーディング クラス EYRecord を作成します。

EYRecord.h

#import <Foundation/Foundation.h> 

@interface ESARecord : NSObject 

//記録を開始
- (void)startRecording; 

//記録を停止
- (void)stopRecording; 

@end

EYRecord.m

#import "ESARecord.h" 
#import <AudioToolbox/AudioToolbox.h> 

#define QUEUE_BUFFER_SIZE 3 // 出力オーディオ キュー バッファ番号
#define kDefaultBufferDurationSeconds 0.03//この値を調整して録音バッファ サイズを 960 にします。これは実際にはまたは 960 に等しい、960 未満の場合を処理する必要がある
#define kDefaultSampleRate 16000 //サンプリング レートを 16000 に定義する

extern NSString * const ESAIntercomNotifationRecordString; 

static BOOL isRecording = NO; 

@interface ESARecord(){ 
  AudioQueueRef _audioQueue; //出力オーディオ再生キュー
  AudioStreamBasicDescriptionForscription;record 
  AudioQueueBufferRef _audioBuffers[QUEUE_BUFFER_SIZE]; //出力オーディオ バッファ
} 
@property (nonatomic, assign) BOOL isRecording; 

@end 

@implementation ESARecord 

- (instancetype)init 
{
  self = [super init]; 
  if (self) { 
    //reset 
    memset(&_recordFormat, 0, sizeof(_recordFormat)); 
    _recordFormat.mSampleRate = kDefaultSampleRate; 
    _recordFormat.mChannelsPerFrame = 1; 
    _recordFormat.mFormatID = kAudioFormatLinearPCM; 

    _ForrecordFlag . = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; 
    _recordFormat.mBitsPerChannel = 16; 
    _recordFormat.mBytesPerPacket = _recordFormat.mBytesPerFrame = (_recordFormat.mBitsPerChannel / 8) * _recordFormat.mChannelsPerFrame; 
    _recordFormat.mFramesPerPacket = 1; 

    //初始化音频输入列列
    AudioQueueNewInput(&_recordFormat, inputBufferHandler, (__bridge void *)(self), NULL, NULL, 0, &_audioQueue); // 推定バッファ サイズを計算します int frames = (int)ceil(kDefaultBufferDurationSeconds * _recordFormat.mSampleRate); 

    int 
    bufferByteSize 
    = frames * _recordFormat.mBytesPerFrame; 

    NSLog(@"bufferByteSize%d", bufferByteSize); 

    //Create buffer 
    for (int i = 0; i < QUEUE_BUFFER_SIZE; i++){ 
      AudioQueueAllocateBuffer(_audioQueue, bufferByteSize, &_audioBuffers[i]) ; 
      AudioQueueEnqueueBuffer(_audioQueue , _audioBuffers[i], 0, NULL); 
    } 
  } 
  return self; 
} 

-(void)startRecording 
{ 
  // 録音を開始
  AudioQueueStart(_audioQueue, NULL);
  isRecording = はい; 
} 

void inputBufferHandler(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime,UInt32 inNumPackets, const AudioStreamPacketDescription *inPacketDesc) { 
if 
  (inNumPackets > 0) { 
    ESARecord *recorder = (__bridge ESARecord*)inUserData; 
    [レコーダー processAudioBuffer:inBuffer withQueue:inAQ]; 
  } 
  
  if (isRecording) { 
    AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL); 
  } 
} 

- (void)processAudioBuffer:(AudioQueueBufferRef)audioQueueBufferRef withQueue:(AudioQueueRef)audioQueueRef 
{
  NSMutableData * dataM = [NSMutableData dataWithBytes:audioQueueBufferRef->mAudioData length:audioQueueBufferRef->mAudioDataByteSize]; 
  
  if (dataM.length < 960) { //処理長が 960 未満、ここでは 00 
    Byte byte[] = {0x00 } ; 
    NSData * zeroData = [[NSData alloc] initWithBytes:byte length:1]; 
    for (NSUInteger i = dataM.length; i < 960; i++) { 
      [dataM appendData:zeroData]; 
    } 
  } 

  // NSLog(@" Real -time recording data--%@", dataM); 
  //dataM を渡す通知を送信する
  [[NSNotificationCenter defaultCenter] postNotificationName:@"EYRecordNotifacation" object:@{@"data" : dataM}]; 
} 

- (void)stopRecording 
{ 
  if (isRecording) 
  { 
    isRecording = NO;
    
    //録音キューを停止してバッファを削除し、セッションを閉じます。成功したかどうかを考慮する必要はありません
    AudioQueueStop(_audioQueue, true); 
    //バッファを削除します。true は録音をすぐに終了することを意味し、false 
    AudioQueueDispose(_audioQueue, true); 
  } 
  NSLog(@"stop recording"); 
} 

@endを終了する前にバッファの処理を終了することを意味します

うまくいかない場合は、EYRecord.m ----> EYRecord.mm を試してください。

おすすめ

転載: blog.csdn.net/ForeverMyheart/article/details/120323844