2017-02-14 6 views
0

2 개의 CAF 파일을 로컬로 단일 파일로 변환하려고합니다. 이 2 개의 CAF 파일은 모노 스트림이며 이상적으로는 한 채널에서 마이크를, 다른 스피커에서 스피커를 가질 수 있도록 스테레오 파일로 제공하고 싶습니다.2 개의 모노 파일을 iOS에서 하나의 스테레오 파일로 변환하는 방법은 무엇입니까?

원래 AVAssetTrack 및 AVMutableCompositionTracks를 사용하여 시작했지만 믹싱을 해결할 수 없습니다. 병합 된 파일은 두 개의 파일을 인터리브 한 단일 모노 스트림이었습니다. 그래서 AVAudioEngine 경로를 선택했습니다.

필자는 두 파일을 입력 노드로 전달하고 믹서에 연결하고 스테레오 믹스를 얻을 수있는 출력 노드를 가질 수 있습니다. 출력 파일에는 스테레오 레이아웃이 있지만 Audacity에서 열어서 스테레오 레이아웃을 볼 때 오디오 데이터가 쓰이지 않는 것 같습니다. installTapOnBus 호출 주위에 dipatch sephamore 신호를 배치하는 것도별로 도움이되지 않았습니다. CoreAudio는 이해하기 어려웠으므로 통찰력은 인정 될 것입니다. ExtAudioFile에 이렇게

// obtain path of microphone and speaker files 
NSString *micPath = [[NSBundle mainBundle] pathForResource:@"microphone" ofType:@"caf"]; 
NSString *spkPath = [[NSBundle mainBundle] pathForResource:@"speaker" ofType:@"caf"]; 
NSURL *micURL = [NSURL fileURLWithPath:micPath]; 
NSURL *spkURL = [NSURL fileURLWithPath:spkPath]; 

// create engine 
AVAudioEngine *engine = [[AVAudioEngine alloc] init]; 

AVAudioFormat *stereoFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:16000 channels:2]; 

AVAudioMixerNode *mainMixer = engine.mainMixerNode; 

// create audio files 
AVAudioFile *audioFile1 = [[AVAudioFile alloc] initForReading:micURL error:nil]; 
AVAudioFile *audioFile2 = [[AVAudioFile alloc] initForReading:spkURL error:nil]; 

// create player input nodes 
AVAudioPlayerNode *apNode1 = [[AVAudioPlayerNode alloc] init]; 
AVAudioPlayerNode *apNode2 = [[AVAudioPlayerNode alloc] init]; 

// attach nodes to the engine 
[engine attachNode:apNode1]; 
[engine attachNode:apNode2]; 

// connect player nodes to engine's main mixer 
stereoFormat = [mainMixer outputFormatForBus:0]; 
[engine connect:apNode1 to:mainMixer fromBus:0 toBus:0 format:audioFile1.processingFormat]; 
[engine connect:apNode2 to:mainMixer fromBus:0 toBus:1 format:audioFile2.processingFormat]; 
[engine connect:mainMixer to:engine.outputNode format:stereoFormat]; 

// start the engine 
NSError *error = nil; 
if(![engine startAndReturnError:&error]){ 
    NSLog(@"Engine failed to start."); 
} 

// create output file 
NSString *mergedAudioFile = [[micPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"merged.caf"]; 
[[NSFileManager defaultManager] removeItemAtPath:mergedAudioFile error:&error]; 
NSURL *mergedURL = [NSURL fileURLWithPath:mergedAudioFile]; 
AVAudioFile *outputFile = [[AVAudioFile alloc] initForWriting:mergedURL settings:[engine.inputNode inputFormatForBus:0].settings error:&error]; 

// write from buffer to output file 
[mainMixer installTapOnBus:0 bufferSize:4096 format:[mainMixer outputFormatForBus:0] block:^(AVAudioPCMBuffer *buffer, AVAudioTime *when){ 
    NSError *error; 
    BOOL success; 
    NSLog(@"Writing"); 
    if((outputFile.length < audioFile1.length) || (outputFile.length < audioFile2.length)){ 
     success = [outputFile writeFromBuffer:buffer error:&error]; 
     NSCAssert(success, @"error writing buffer data to file, %@", [error localizedDescription]); 
     if(error){ 
      NSLog(@"Error: %@", error); 
     } 
    } 
    else{ 
     [mainMixer removeTapOnBus:0]; 
     NSLog(@"Done writing"); 
    } 
}]; 

}

+0

당신은 당신이 쓰고있는 AVAudioFile에 강한 참조를 보유하고 있습니까 : 여기

은 예입니다? – dave234

+0

@ Dave, 쓰기 전에 outputFile이 존재하지 않습니다. 강력한 참조 측면에서, 나는 audioFile을 mergedURL에 쓰도록 설정하고 있는데, 이는 mergedAudioFile의 fileURLWithPath입니다. outputFile을 참조하는 다른 객체/변수는 없으며 installTapOnBus 호출 후에도이를 파기하지는 않습니다. – A21

+0

이 방법의 약점 중 하나는 파일의 지속 시간이 하나가 될 때까지 기다려야한다는 것입니다. 즉, AVAudioEngine을 고수한다면 두 파일을 먼저 재생 해 볼 수 있습니다. 그런 다음 해당 단계가 완료되면 탭을 설치하고 파일에 씁니다. 하지만 내가 직접해야한다면 C API를 사용할 것이다. – dave234

답변

2

는 세 개의 파일, 3 개 버퍼를 포함한다. 읽기 용 모노 2 개와 쓰기 용 스테레오 1 개. 루프에서 각 모노 파일은 오디오의 작은 부분을 모노 출력 버퍼로 읽은 다음 스테레오 버퍼의 올바른 "절반"으로 복사합니다. 그런 다음 스테레오 버퍼가 가득차면 버퍼 파일을 출력 파일에 씁니다. 두 모노 파일 모두 읽기가 끝날 때까지 반복하십시오 (하나의 모노 파일이 다른 파일보다 길면 0을 기록).

나를 위해 가장 문제가되는 부분은 파일 형식을 올바르게 얻는 것입니다. 핵심 오디오는 매우 특정한 형식을 원합니다. 다행히도 일반적인 형식의 생성을 단순화하기 위해 AVAudioFormat이 존재합니다.

각 오디오 파일 판독기/기록기에는 데이터가 저장된 형식 (file_format)과 리더/라이터 (client_format)에서 들어오고 나오는 형식을 나타내는 형식의 두 가지 형식이 있습니다. 형식이 다른 경우 리더/라이터에 형식 변환기가 내장되어 있습니다.

-(void)soTest{ 


    //This is what format the readers will output 
    AVAudioFormat *monoClienFormat = [[AVAudioFormat alloc]initWithCommonFormat:AVAudioPCMFormatInt16 sampleRate:44100.0 channels:1 interleaved:0]; 

    //This is the format the writer will take as input 
    AVAudioFormat *stereoClientFormat = [[AVAudioFormat alloc]initWithCommonFormat:AVAudioPCMFormatInt16 sampleRate:44100 channels:2 interleaved:0]; 

    //This is the format that will be written to storage. It must be interleaved. 
    AVAudioFormat *stereoFileFormat = [[AVAudioFormat alloc]initWithCommonFormat:AVAudioPCMFormatInt16 sampleRate:44100 channels:2 interleaved:1]; 




    NSURL *leftURL = [NSBundle.mainBundle URLForResource:@"left" withExtension:@"wav"]; 
    NSURL *rightURL = [NSBundle.mainBundle URLForResource:@"right" withExtension:@"wav"]; 

    NSString *stereoPath = [documentsDir() stringByAppendingPathComponent:@"stereo.wav"]; 
    NSURL *stereoURL = [NSURL URLWithString:stereoPath]; 

    ExtAudioFileRef leftReader; 
    ExtAudioFileRef rightReader; 
    ExtAudioFileRef stereoWriter; 


    OSStatus status = 0; 

    //Create readers and writer 
    status = ExtAudioFileOpenURL((__bridge CFURLRef)leftURL, &leftReader); 
    if(status)printf("error %i",status);//All the ExtAudioFile functins return a non-zero status if there's an error, I'm only checking one to demonstrate, but you should be checking all the ExtAudioFile function returns. 
    ExtAudioFileOpenURL((__bridge CFURLRef)rightURL, &rightReader); 
    //Here the file format is set to stereo interleaved. 
    ExtAudioFileCreateWithURL((__bridge CFURLRef)stereoURL, kAudioFileCAFType, stereoFileFormat.streamDescription, nil, kAudioFileFlags_EraseFile, &stereoWriter); 


    //Set client format for readers and writer 
    ExtAudioFileSetProperty(leftReader, kExtAudioFileProperty_ClientDataFormat, sizeof(AudioStreamBasicDescription), monoClienFormat.streamDescription); 
    ExtAudioFileSetProperty(rightReader, kExtAudioFileProperty_ClientDataFormat, sizeof(AudioStreamBasicDescription), monoClienFormat.streamDescription); 
    ExtAudioFileSetProperty(stereoWriter, kExtAudioFileProperty_ClientDataFormat, sizeof(AudioStreamBasicDescription), stereoClientFormat.streamDescription); 


    int framesPerRead = 4096; 
    int bufferSize = framesPerRead * sizeof(SInt16); 

    //Allocate memory for the buffers 
    AudioBufferList *leftBuffer = createBufferList(bufferSize,1); 
    AudioBufferList *rightBuffer = createBufferList(bufferSize,1); 
    AudioBufferList *stereoBuffer = createBufferList(bufferSize,2); 

    //ExtAudioFileRead takes an ioNumberFrames argument. On input the number of frames you want, on otput it's the number of frames you got. 0 means your done. 
    UInt32 leftFramesIO = framesPerRead; 
    UInt32 rightFramesIO = framesPerRead; 



    while (leftFramesIO || rightFramesIO) { 
     if (leftFramesIO){ 
      //If frames to read is less than a full buffer, zero out the remainder of the buffer 
      int framesRemaining = framesPerRead - leftFramesIO; 
      if (framesRemaining){ 
       memset(((SInt16 *)leftBuffer->mBuffers[0].mData) + framesRemaining, 0, sizeof(SInt16) * framesRemaining); 
      } 
      //Read into left buffer 
      leftBuffer->mBuffers[0].mDataByteSize = leftFramesIO * sizeof(SInt16); 
      ExtAudioFileRead(leftReader, &leftFramesIO, leftBuffer); 
     } 
     else{ 
      //set to zero if no more frames to read 
      memset(leftBuffer->mBuffers[0].mData, 0, sizeof(SInt16) * framesPerRead); 
     } 

     if (rightFramesIO){ 
      int framesRemaining = framesPerRead - rightFramesIO; 
      if (framesRemaining){ 
       memset(((SInt16 *)rightBuffer->mBuffers[0].mData) + framesRemaining, 0, sizeof(SInt16) * framesRemaining); 
      } 
      rightBuffer->mBuffers[0].mDataByteSize = rightFramesIO * sizeof(SInt16); 
      ExtAudioFileRead(rightReader, &rightFramesIO, rightBuffer); 
     } 
     else{ 
      memset(rightBuffer->mBuffers[0].mData, 0, sizeof(SInt16) * framesPerRead); 
     } 


     UInt32 stereoFrames = MAX(leftFramesIO, rightFramesIO); 

     //copy left to stereoLeft and right to stereoRight 
     memcpy(stereoBuffer->mBuffers[0].mData, leftBuffer->mBuffers[0].mData, sizeof(SInt16) * stereoFrames); 
     memcpy(stereoBuffer->mBuffers[1].mData, rightBuffer->mBuffers[0].mData, sizeof(SInt16) * stereoFrames); 

     //write to file 
     stereoBuffer->mBuffers[0].mDataByteSize = stereoFrames * sizeof(SInt16); 
     stereoBuffer->mBuffers[1].mDataByteSize = stereoFrames * sizeof(SInt16); 
     ExtAudioFileWrite(stereoWriter, stereoFrames, stereoBuffer); 

    } 

    ExtAudioFileDispose(leftReader); 
    ExtAudioFileDispose(rightReader); 
    ExtAudioFileDispose(stereoWriter); 

    freeBufferList(leftBuffer); 
    freeBufferList(rightBuffer); 
    freeBufferList(stereoBuffer); 

} 

AudioBufferList *createBufferList(int bufferSize, int numberBuffers){ 
    assert(bufferSize > 0 && numberBuffers > 0); 
    int bufferlistByteSize = sizeof(AudioBufferList); 
    bufferlistByteSize += sizeof(AudioBuffer) * (numberBuffers - 1); 
    AudioBufferList *bufferList = malloc(bufferlistByteSize); 
    bufferList->mNumberBuffers = numberBuffers; 
    for (int i = 0; i < numberBuffers; i++) { 
     bufferList->mBuffers[i].mNumberChannels = 1; 
     bufferList->mBuffers[i].mData = malloc(bufferSize); 
    } 
    return bufferList; 
}; 
void freeBufferList(AudioBufferList *bufferList){ 
    for (int i = 0; i < bufferList->mNumberBuffers; i++) { 
     free(bufferList->mBuffers[i].mData); 
    } 
    free(bufferList); 
} 
NSString *documentsDir(){ 
    static NSString *path = NULL; 
    if(!path){ 
     path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, 1).firstObject; 
    } 
    return path; 
} 
+0

각 채널에 출력이없는 스테레오 파일을 다시 가져오고 있습니다. 입력 된 모노 파일은 CAF 형식이지만 형식이 많이 벗어나는 것은 아닙니다. – A21

+0

ExtAudioFile 반환 값을 모두 확인하고 있습니까? – dave234

+0

그래, 문제는 EAF 출력 파일 생성에 있습니다. 내가 전달하는 URL은 확장자입니다. ".caf"는 ".wav"와 비교됩니다. kAudioFormatUnsupportedDataFormatError를 참조하는 1718449215의 OSStatus 오류를 표시합니다. – A21