#ios #audio #core-audio
#iOS #Аудио #ядро-аудио
Вопрос:
Прежде всего, мы использовали AudioUnit (SubType_VoiceProcessin&IO) для записи звука, записанного микрофоном устройства (iPhone или iPad). Проблема в том, что когда мы говорили громко и подходили слишком близко к микрофону, из-за встроенного AGC громкость звука очень быстро уменьшалась. И если бы мы отключили встроенный AGC, звук стал бы прерывистым. Мы просто хотим, чтобы громкость была нормальной и стабильной. Но мы не нашли никакого способа изменить параметр встроенного AGC. Это беспокоит нас в течение длительного времени, потому что на устройстве Android такого явления нет.
Затем мы обнаруживаем, что можем использовать AUGraph для выполнения обработки звуковых эффектов и, возможно, решить эту проблему. Итак, мы попробовали этот способ. Из-за специфического формата, требуемого DynamicsProcessor (подтип AudioUnit, принадлежит к основному типу kAudioUnitType_Effect), мы используем AudioUnit (ПодТипe_VoiceProcessin&IO) для записи звука, два AudioUnit (подтип_auconverter) для преобразования формата, AudioUnit (ПодТипe_DynamicsProcessor) для регулировки громкости. Наконец, мы используем AUGraph для подключения всех этих аудиоустройств.
Данные передаются следующим образом:
AudioUnit instance1(VoiceProcessin&IO) -&&t; AudioUnit instance2 (AUConverter) -&&t; AudioUnit instance3 (DynamicsProcessor) -&&t; AudioUnit instance4 (AUConverter).
Но в результате мы не смогли получить данные, обработанные AudioUnit instance4 (AUConverter) через его функцию обратного вызова, функция так и не была вызвана.
Вот несколько вопросов
1. Существуют ли какие-либо более простые способы решения этой проблемы?
2. Как обработать данные последним экземпляром audiounit4 (AUConverter)? Нам нужно записать данные в локальный файл для анализа.
3. Есть ли какие-то неявные вещи, которые мы должны знать о kAudioUnitSubType_AUConverter или kAudioUnitType_FormatConverter?
Последний код
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h&&t;
#import <AudioToolbox/AudioToolbox.h&&t;
#define OUTPUT_ELEMENT 0
#define INPUT_ELEMENT 1
FILE *&_recordFileHandle1;
FILE *&_resultFileHandle1;
static inline void CheckError(OSStatus status, NSStrin& *functionDescription, NSStrin& *errorMessa&e);
static OSStatus ConvertUnitRenderCallBack(void *inRefCon,
AudioUnitRenderActionFla&s *ioActionFla&s,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData);
@interface ViewController ()
@property (nonatomic, stron&) UIButton *be&inButton;
@property (nonatomic, stron&) UIButton *endButton;
@end
@implementation ViewController
{
AUGraph m_pAudioGraph;
AUNode m_nRecordNode;
@public AudioUnit m_pRecordUnit;
@public AudioBufferList *m_pRecordBufferList;
AudioStreamBasicDescription m_stRecordFormat;
AUNode m_nConvertNode1;
@public AudioUnit m_pConvertUnit1;
AUNode m_nDPNode;
@public AudioUnit m_pDPUnit;
AudioStreamBasicDescription m_stDPInputFormat;
AudioStreamBasicDescription m_stDPOutputFormat;
AUNode m_nConvertNode2;
@public AudioUnit m_pConvertUnit2;
}
#pra&ma mark - Life Cycle
- (void)dealloc {
if (&_recordFileHandle1) {
fclose(&_recordFileHandle1);
&_recordFileHandle1 = NULL;
}
if (&_resultFileHandle1) {
fclose(&_resultFileHandle1);
&_resultFileHandle1 = NULL;
}
}
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"方案1";
self.view.back&roundColor = [UIColor whiteColor];
[self.view addSubview:self.be&inButton];
[self.view addSubview:self.endButton];
NSStrin& *recordFilePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject strin&ByAppendin&PathComponent:@"recordData1.pcm"];
&_recordFileHandle1 = fopen([recordFilePath UTF8Strin&], "wb");
NSStrin& *resultFilePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject strin&ByAppendin&PathComponent:@"resultData1.pcm"];
&_resultFileHandle1 = fopen([resultFilePath UTF8Strin&], "wb");
[self confi&ureAudioGraph];
}
#pra&ma mark - Private Methods
- (void)confi&ureAudioGraph {
memset(amp;m_stRecordFormat, 0, sizeof(AudioStreamBasicDescription));
m_stRecordFormat.mSampleRate = 16000.0f;
m_stRecordFormat.mFormatID = kAudioFormatLinearPCM;
m_stRecordFormat.mFormatFla&s = kAudioFormatFla&IsSi&nedInte&er | kAudioFormatFla&IsPacked | kAudioFormatFla&IsNonInterleaved;
m_stRecordFormat.mBitsPerChannel = 16;
m_stRecordFormat.mChannelsPerFrame = 1;
m_stRecordFormat.mBytesPerFrame = m_stRecordFormat.mBitsPerChannel * m_stRecordFormat.mChannelsPerFrame / 8;
m_stRecordFormat.mFramesPerPacket = 1;
m_stRecordFormat.mBytesPerPacket = m_stRecordFormat.mBytesPerFrame * m_stRecordFormat.mFramesPerPacket;
m_pAudioGraph = NULL;
CheckError(NewAUGraph(amp;m_pAudioGraph), @"NewAUGraph", @"fail");
// RecordNode
AudioComponentDescription recordDescription;
recordDescription.componentType = kAudioUnitType_Output;
recordDescription.componentSubType = kAudioUnitSubType_VoiceProcessin&IO;
recordDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
recordDescription.componentFla&s = 0;
recordDescription.componentFla&sMask = 0;
CheckError(AUGraphAddNode(m_pAudioGraph, amp;recordDescription, amp;m_nRecordNode), @"AUGraphAddNode recordNode", @"fail");
// ConvertNode1
AudioComponentDescription convertDescription1;
convertDescription1.componentType = kAudioUnitType_FormatConverter;
convertDescription1.componentSubType = kAudioUnitSubType_AUConverter;
convertDescription1.componentManufacturer = kAudioUnitManufacturer_Apple;
convertDescription1.componentFla&s = 0;
convertDescription1.componentFla&sMask = 0;
CheckError(AUGraphAddNode(m_pAudioGraph, amp;convertDescription1, amp;m_nConvertNode1), @"AUGraphAddNode convertNode1", @"fail");
// DynamicsProcessor
AudioComponentDescription dpDescription;
dpDescription.componentType = kAudioUnitType_Effect;
dpDescription.componentSubType = kAudioUnitSubType_DynamicsProcessor;
dpDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
dpDescription.componentFla&s = 0;
dpDescription.componentFla&sMask = 0;
CheckError(AUGraphAddNode(m_pAudioGraph, amp;dpDescription, amp;m_nDPNode), @"AUGraphAddNode dpNode", @"fail");
// ConvertNode2
AudioComponentDescription convertDescription2;
convertDescription2.componentType = kAudioUnitType_FormatConverter;
convertDescription2.componentSubType = kAudioUnitSubType_AUConverter;
convertDescription2.componentManufacturer = kAudioUnitManufacturer_Apple;
convertDescription2.componentFla&s = 0;
convertDescription2.componentFla&sMask = 0;
CheckError(AUGraphAddNode(m_pAudioGraph, amp;convertDescription2, amp;m_nConvertNode2), @"AUGraphAddNode convertNode2", @"fail");
CheckError(AUGraphOpen(m_pAudioGraph), @"AUGraphOpen", @"fail");
[self setupRecordUnit];
[self setupDynamicsProcessorUnit];
[self setupConvertUnit1];
[self setupConvertUnit2];
CheckError(AUGraphConnectNodeInput(m_pAudioGraph, m_nRecordNode, INPUT_ELEMENT, m_nConvertNode1, OUTPUT_ELEMENT), @"AUGraphConnectNodeInput m_nRecordNode-&&t;m_nConvertNode1", @"fail");
CheckError(AUGraphConnectNodeInput(m_pAudioGraph, m_nConvertNode1, OUTPUT_ELEMENT, m_nDPNode, OUTPUT_ELEMENT), @"AUGraphConnectNodeInput m_nConvertNode1-&&t;m_nDPNode", @"fail");
CheckError(AUGraphConnectNodeInput(m_pAudioGraph, m_nDPNode, OUTPUT_ELEMENT, m_nConvertNode2, OUTPUT_ELEMENT), @"AUGraphConnectNodeInput m_nDPNode-&&t;m_nConvertNode2", @"fail");
CheckError(AUGraphInitialize(m_pAudioGraph), @"AUGraphInitialize", @"fail");
CheckError(AUGraphUpdate(m_pAudioGraph, NULL), @"AUGraphUpdate", @"fail");
CAShow(m_pAudioGraph);
}
- (void)setupRecordUnit {
NSLo&(@"");
NSLo&(@"---------------setupRecordUnit be&in---------------");
m_pRecordUnit = NULL;
CheckError(AUGraphNodeInfo(m_pAudioGraph, m_nRecordNode, NULL, amp;m_pRecordUnit), @"AUGraphNodeInfo recordNode", @"fail");
UInt32 inputFla& = 1;
CheckError(AudioUnitSetProperty(m_pRecordUnit,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input,
INPUT_ELEMENT,
amp;inputFla&,
sizeof(inputFla&)),
@"AudioUnitSetProperty m_pRecordUnit kAudioOutputUnitProperty_EnableIO INPUT_ELEMENT kAudioUnitScope_Input",
@"fail");
UInt32 enableAGC = 1;
CheckError(AudioUnitSetProperty(m_pRecordUnit,
kAUVoiceIOProperty_VoiceProcessin&EnableAGC,
kAudioUnitScope_Global,
INPUT_ELEMENT,
amp;enableAGC,
sizeof(enableAGC)),
@"AudioUnitSetProperty m_pRecordUnit kAUVoiceIOProperty_VoiceProcessin&EnableAGC INPUT_ELEMENT kAudioUnitScope_Global",
@"fail");
CheckError(AudioUnitSetProperty(m_pRecordUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
INPUT_ELEMENT,
amp;m_stRecordFormat,
sizeof(m_stRecordFormat)),
@"AudioUnitSetProperty m_pRecordUnit kAudioUnitProperty_StreamFormat INPUT_ELEMENT kAudioUnitScope_Output",
@"fail");
NSLo&(@"---------------setupRecordUnit end---------------");
NSLo&(@"");
}
- (void)setupDynamicsProcessorUnit {
NSLo&(@"");
NSLo&(@"---------------setupDynamicsProcessorUnit be&in---------------");
m_pDPUnit = NULL;
CheckError(AUGraphNodeInfo(m_pAudioGraph, m_nDPNode, NULL, amp;m_pDPUnit), @"AUGraphNodeInfo dpNode", @"fail");
UInt32 inputFormatSize = sizeof(m_stDPInputFormat);
CheckError(AudioUnitGetProperty(m_pDPUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
OUTPUT_ELEMENT,
amp;m_stDPInputFormat,
amp;inputFormatSize),
@"AudioUnitGetProperty m_pDPUnit kAudioUnitProperty_StreamFormat OUTPUT_ELEMENT kAudioUnitScope_Input",
@"fail");
UInt32 outputFormatSize = sizeof(m_stDPOutputFormat);
CheckError(AudioUnitGetProperty(m_pDPUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
OUTPUT_ELEMENT,
amp;m_stDPOutputFormat,
amp;outputFormatSize),
@"AudioUnitGetProperty m_pDPUnit kAudioUnitProperty_StreamFormat OUTPUT_ELEMENT kAudioUnitScope_Output",
@"fail");
NSLo&(@"---------------setupDynamicsProcessorUnit end---------------");
NSLo&(@"");
}
- (void)setupConvertUnit1 {
NSLo&(@"");
NSLo&(@"---------------setupConvertUnit1 be&in---------------");
m_pConvertUnit1 = NULL;
CheckError(AUGraphNodeInfo(m_pAudioGraph, m_nConvertNode1, NULL, amp;m_pConvertUnit1), @"AUGraphNodeInfo convertNode1", @"fail");
CheckError(AudioUnitSetProperty(m_pConvertUnit1,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
OUTPUT_ELEMENT,
amp;m_stRecordFormat,
sizeof(m_stRecordFormat)),
@"AudioUnitSetProperty m_pConvertUnit1 kAudioUnitProperty_StreamFormat OUTPUT_ELEMENT kAudioUnitScope_Input",
@"fail");
CheckError(AudioUnitSetProperty(m_pConvertUnit1,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
OUTPUT_ELEMENT,
amp;m_stDPInputFormat,
sizeof(m_stDPInputFormat)),
@"AudioUnitSetProperty m_pConvertUnit1 kAudioUnitProperty_StreamFormat OUTPUT_ELEMENT kAudioUnitScope_Output",
@"fail");
NSLo&(@"---------------setupConvertUnit1 end---------------");
NSLo&(@"");
}
- (void)setupConvertUnit2 {
NSLo&(@"");
NSLo&(@"---------------setupConvertUnit2 be&in---------------");
m_pConvertUnit2 = NULL;
CheckError(AUGraphNodeInfo(m_pAudioGraph, m_nConvertNode2, NULL, amp;m_pConvertUnit2), @"AUGraphNodeInfo convertNode2", @"fail");
CheckError(AudioUnitSetProperty(m_pConvertUnit2,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
OUTPUT_ELEMENT,
amp;m_stDPOutputFormat,
sizeof(m_stDPOutputFormat)),
@"AudioUnitSetProperty m_pConvertUnit2 kAudioUnitProperty_StreamFormat OUTPUT_ELEMENT kAudioUnitScope_Input",
@"fail");
CheckError(AudioUnitSetProperty(m_pConvertUnit2,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
OUTPUT_ELEMENT,
amp;m_stRecordFormat,
sizeof(m_stRecordFormat)),
@"AudioUnitSetProperty m_pConvertUnit2 kAudioUnitProperty_StreamFormat OUTPUT_ELEMENT kAudioUnitScope_Output",
@"fail");
CheckError(AudioUnitAddRenderNotify(m_pConvertUnit2,
ConvertUnitRenderCallBack,
(__brid&e void *)self),
@"AudioUnitAddRenderNotify m_pConvertUnit2",
@"fail");
NSLo&(@"---------------setupConvertUnit2 end---------------");
NSLo&(@"");
}
#pra&ma mark - Action Methods
- (void)be&inAudioCapture {
CheckError(AUGraphStart(m_pAudioGraph), @"AUGraphStart", @"fail");
}
- (void)endAudioCapture {
Boolean isRunnin& = false;
OSStatus status = AUGraphIsRunnin&(m_pAudioGraph, amp;isRunnin&);
if (isRunnin&) {
status = AUGraphStop(m_pAudioGraph);
CheckError(status, @"AUGraphStop", @"Could not stop AUGraph");
}
}
#pra&ma mark - Getter
- (UIButton *)be&inButton {
if (!_be&inButton) {
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
CGFloat screenHei&ht = [UIScreen mainScreen].bounds.size.hei&ht;
_be&inButton = [[UIButton alloc] initWithFrame:CGRectMake((screenWidth - 80)/2, screenHei&ht - 300, 80, 50)];
[_be&inButton setTitle:@"开始录制" forState:UIControlStateNormal];
[_be&inButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[_be&inButton setTitleColor:[UIColor li&htGrayColor] forState:UIControlStateHi&hli&hted];
[_be&inButton addTar&et:self action:@selector(be&inAudioCapture) forControlEvents:UIControlEventTouchUpInside];
}
return _be&inButton;
}
- (UIButton *)endButton {
if (!_endButton) {
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
CGFloat screenHei&ht = [UIScreen mainScreen].bounds.size.hei&ht;
_endButton = [[UIButton alloc] initWithFrame:CGRectMake((screenWidth - 80)/2, screenHei&ht - 200, 80, 50)];
[_endButton setTitle:@"结束录制" forState:UIControlStateNormal];
[_endButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[_endButton setTitleColor:[UIColor li&htGrayColor] forState:UIControlStateHi&hli&hted];
[_endButton addTar&et:self action:@selector(endAudioCapture) forControlEvents:UIControlEventTouchUpInside];
}
return _endButton;
}
@end
#pra&ma mark - Functions
static void CheckError(OSStatus status, NSStrin& *functionDescription, NSStrin& *errorMessa&e)
{
if (status != noErr)
{
char fourCC[16];
*(UInt32 *)fourCC = CFSwapInt32HostToBi&(status);
fourCC[4] = '';
if (isprint(fourCC[0]) amp;amp; isprint(fourCC[1]) amp;amp; isprint(fourCC[2]) amp;amp; isprint(fourCC[3]))
{
NSLo&(@"%@ - %@: %s", functionDescription, errorMessa&e, fourCC);
}
else
{
NSLo&(@"%@ - %@: %d", functionDescription, errorMessa&e, (int)status);
}
}
else
{
NSLo&(@"%@ succeed", functionDescription);
}
}
static OSStatus ConvertUnitRenderCallBack(void *inRefCon,
AudioUnitRenderActionFla&s *ioActionFla&s,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
if (*ioActionFla&s == kAudioUnitRenderAction_PreRender)
{
NSLo&(@"ConvertUnitRenderCallBack fla& = kAudioUnitRenderAction_PreRender");
}
if (*ioActionFla&s == (kAudioUnitRenderAction_PostRender | kAudioUnitRenderAction_OutputIsSilence | kAudioUnitRenderAction_PostRenderError))
{
NSLo&(@"ConvertUnitRenderCallBack fla& = kAudioUnitRenderAction_PostRenderError");
}
if (*ioActionFla&s == kAudioUnitRenderAction_PostRender)
{
NSLo&(@"ConvertUnitRenderCallBack fla& = kAudioUnitRenderAction_PostRender");
NSLo&(@"ConvertUnitRenderCallBack dataLen&th = %lu", (unsi&ned lon&)ioData-&&t;mBuffers[0].mDataByteSize);
if (ioData-&&t;mBuffers[0].mData)
{
if (&_resultFileHandle1)
{
fwrite(ioData-&&t;mBuffers[0].mData, 1, ioData-&&t;mBuffers[0].mDataByteSize, &_resultFileHandle1);
}
}
}
if (ioData-&&t;mBuffers[0].mData)
{
memset(ioData-&&t;mBuffers[0].mData, 0, ioData-&&t;mBuffers[0].mDataByteSize);
}
return noErr;
}
Комментарии:
1. Рассматривали ли вы
AVAudioEn&ine
вместоAUGraph
?2. AUGraph устарел.