Добавяне на аудио буфер [от файл] към 'живо' аудио буфер [запис във файл]

Какво се опитвам да направя:

Записвайте до определена продължителност на аудио/видео, където полученият изходен файл ще има добавена предварително дефинирана фонова музика от външен аудиофайл - без допълнително кодиране/експортиране след запис.

Сякаш записвате видео с помощта на приложението Camera на iPhone и всички записани видеоклипове в „Camera Roll“ имат фонови песни. Без експортиране или зареждане след приключване на записа и не в отделен AudioTrack.


Как се опитвам да постигна това:

Чрез използване на AVCaptureSession, в делегатния метод, през който се предават (CMSampleBufferRef) примерните буфери, аз ги насочвам към AVAssetWriter за запис във файл. Тъй като не искам множество аудиозаписи в моя изходен файл, не мога да предам фоновата музика през отделен AVAssetWriterInput, което означава, че трябва да добавя фоновата музика към всеки примерен буфер от записа, докато е записване, за да избегнете необходимостта от обединяване/експортиране след записване.

Музикалният фон е специфичен, предварително дефиниран аудио файл (формат/кодек: m4a aac) и няма нужда от редактиране във времето, а само добавяне под на целия запис, от началото до края. Записът никога няма да бъде по-дълъг от фоновия музикален файл.

Преди да започна записа във файл, подготвих и AVAssetReader, четене на посочения аудио файл.

Някакъв псевдокод(изключени нишки):

-(void)startRecording
{
    /*
        Initialize writer and reader here: [...]
    */
    
    backgroundAudioTrackOutput = [AVAssetReaderTrackOutput 
                            assetReaderTrackOutputWithTrack:
                                backgroundAudioTrack 
                            outputSettings:nil];

    if([backgroundAudioReader canAddOutput:backgroundAudioTrackOutput])
        [backgroundAudioReader addOutput:backgroundAudioTrackOutput];
    else
        NSLog(@"This doesn't happen");

    [backgroundAudioReader startReading];

    /* Some more code */

    recording = YES;
}
- (void)captureOutput:(AVCaptureOutput *)captureOutput 
             didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer 
             fromConnection:(AVCaptureConnection *)connection
{
    if(!recording)
        return;

    if(videoConnection)
        [self writeVideoSampleBuffer:sampleBuffer];
    else if(audioConnection)
        [self writeAudioSampleBuffer:sampleBuffer];
}

AVCaptureSession вече предава видеото от камерата и аудиото от микрофона и просто чака BOOL recording да бъде настроен на YES. Това не е точно как правя това, а кратко, някак еквивалентно представяне. Когато делегатният метод получи CMSampleBufferRef от тип Audio, аз извиквам моя собствен метод writeAudioSamplebuffer:sampleBuffer. Ако това трябваше да се направи нормално, без фонова следа, както се опитвам да направя, просто бих поставил нещо подобно: [assetWriterAudioInput appendSampleBuffer:sampleBuffer]; вместо да извиквам моя метод. В моя случай обаче трябва да припокрия два буфера, преди да го напиша:

-(void)writeAudioSamplebuffer:(CMSampleBufferRef)recordedSampleBuffer
{
    CMSampleBufferRef backgroundSampleBuffer = 
                     [backgroundAudioTrackOutput copyNextSampleBuffer];

    /* DO MAGIC HERE  */
    CMSampleBufferRef resultSampleBuffer = 
                         [self overlapBuffer:recordedSampleBuffer 
                            withBackgroundBuffer:backgroundSampleBuffer];
    /* END MAGIC HERE */

    [assetWriterAudioInput appendSampleBuffer:resultSampleBuffer];
}

Проблемът:

Трябва да добавя инкрементални примерни буфери от локален файл към идващите буфери на живо. Методът, който създадох с име overlapBuffer:withBackgroundBuffer:, не прави много в момента. Знам как да извличам AudioBufferList, AudioBuffer и mData и т.н. от CMSampleBufferRef, но не съм сигурен как всъщност да ги събера заедно - обаче - не съм бил в състояние да тества различни начини за това, защото истинският проблем се случва преди това. Преди Магията да се случи, притежавам два CMSampleBufferRef, един получен от микрофон, един прочетен от файл и това е проблемът:

Буферът за проби, получен от файла с фонова музика, е различен от този, който получавам от сесията за запис. Изглежда, че извикването до [self.backgroundAudioTrackOutput copyNextSampleBuffer]; получава голям брой проби. Осъзнавам, че това може да е очевидно за някои хора, но никога досега не съм бил на това ниво на медийна технология. Сега виждам, че е било пожелателно мислене да извиквам copyNextSampleBuffer всеки път, когато получа sampleBuffer от сесията, но не знам кога/къде да го поставя.

Доколкото мога да преценя, сесията за запис дава една аудио-проба във всеки буфер за проби, докато четецът на файлове дава множество проби във всеки буфер за проби. Мога ли по някакъв начин да създам брояч за преброяване на всяка получена записана проба/буфери и след това да използвам първия file-sampleBuffer за извличане на всяка проба, докато текущият file-sampleBuffer няма повече проби „за даване“, и след това извикайте [..copyNext..] и направете същото с този буфер?

Тъй като имам пълен контрол както над записа, така и върху кодеците, форматите и т.н. на файла, надявам се, че такова решение няма да развали „подравняването“/синхронизирането на аудиото. Като се има предвид, че и двете проби имат една и съща sampleRate, това все още може да е проблем?


Забележка

Дори не съм сигурен дали това е възможно, но не виждам непосредствена причина защо не трябва. Също така си струва да се спомене, че когато се опитвам да използвам видео файл вместо аудио файл и се опитвам непрекъснато да изтеглям видео sampleBuffers, те се подравняват перфектно.


person Sti    schedule 19.09.2014    source източник


Отговори (1)


Не съм запознат с AVCaptureOutput, тъй като всичките ми звукови/музикални сесии бяха създадени с помощта на AudioToolbox вместо AVFoundation. Въпреки това, предполагам, че трябва да можете да зададете размера на буфера за улавяне на запис. Ако не, и все още получавате само една проба, бих ви препоръчал да съхранявате всяка отделна информация, получена от изхода за улавяне, в допълнителен буфер. Когато спомагателният буфер достигне същия размер като буфера за четене на файлове, извикайте [self overlapBuffer:auxiliarSampleBuffer withBackgroundBuffer:backgroundSampleBuffer];

Надявам се това да ви помогне. Ако не, мога да дам пример как да направите това с помощта на CoreAudio. С помощта на CoreAudio успях да получа 1024 LCPM буфер за проби както от заснемане на микрофон, така и от четене на файлове. Така че припокриването е незабавно.

person CRoig    schedule 25.09.2014
comment
Благодаря ти! Това е много добра идея. Бях толкова съсредоточен в опитите си да намеря обратния начин да направя това (да взема една проба от много, вместо да чакам една да стане много). Това обаче би означавало, че писането няма да става непрекъснато чрез запис, а на партиди, така че ако това няма да повлияе на качеството или честотата на кадрите, това може да е възможно решение! Много бих искал пример, дори и с CoreAudio. - person Sti; 25.09.2014
comment
Тук: zerokidz.com/audiograph/Download.html има великолепен проект, свързан със създаването на аудио сесии с помощта на CoreAudio. Използвал съм го като модел за създаване на мои персонализирани сесии за iOS. - person CRoig; 26.09.2014
comment
След това трябва да се съсредоточите върху readAudioFilesIntoMemory метод в MixerHostAudio.m. Там soundStructArray[audioFile].audioDataLeft съхранява семплите от пълния аудиофайл. Препоръчвам ви да ги копирате в нов буфер. След това, ако погледнете micLineInCallback в MixerHostAudio.m, ще намерите обратното извикване, което обработва входните проби. Ще намерите входните проби в sampleBufferLeft по кадри. След това, ако само припокривате тези проби подредено (по отношение на правилното времево клеймо) върху файловия буфер, ще постигнете целта си. - person CRoig; 26.09.2014