Какво се опитвам да направя:
Записвайте до определена продължителност на аудио/видео, където полученият изходен файл ще има добавена предварително дефинирана фонова музика от външен аудиофайл - без допълнително кодиране/експортиране след запис.
Сякаш записвате видео с помощта на приложението 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, те се подравняват перфектно.