要件: 最近、会社は建物のインターホン機能を実行する必要があります: 屋外ステーション (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 を試してください。