AVAssetReader を使用してビデオ ファイルを読み込んで、オーディオを CoreAudio に渡して処理 (エフェクトなどを追加) してから、AVAssetWriter を使用してディスクに保存しようとしています。出力ノードの AudioComponentDescription に componentSubType を RemoteIO として設定すると、スピーカーから正しく再生されることを指摘したいと思います。これにより、AUGraph が適切にセットアップされていることを確信できます。動作が聞こえるからです。ただし、subType を GenericOutput に設定しているので、自分でレンダリングを行い、調整されたオーディオを取り戻すことができます。
オーディオを読み込んでいて、CMSampleBufferRef を copyBuffer に渡します。これにより、オーディオが循環バッファに入れられ、後で読み込まれます。
- (void)copyBuffer:(CMSampleBufferRef)buf {
if (_readyForMoreBytes == NO)
{
return;
}
AudioBufferList abl;
CMBlockBufferRef blockBuffer;
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(buf, NULL, &abl, sizeof(abl), NULL, NULL, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &blockBuffer);
UInt32 size = (unsigned int)CMSampleBufferGetTotalSampleSize(buf);
BOOL bytesCopied = TPCircularBufferProduceBytes(&circularBuffer, abl.mBuffers[0].mData, size);
if (!bytesCopied){
/
_readyForMoreBytes = NO;
if (size > kRescueBufferSize){
NSLog(@"Unable to allocate enought space for rescue buffer, dropping audio frame");
} else {
if (rescueBuffer == nil) {
rescueBuffer = malloc(kRescueBufferSize);
}
rescueBufferSize = size;
memcpy(rescueBuffer, abl.mBuffers[0].mData, size);
}
}
CFRelease(blockBuffer);
if (!self.hasBuffer && bytesCopied > 0)
{
self.hasBuffer = YES;
}
}
次に、processOutput を呼び出します。これにより、outputUnit で手動の reder が実行されます。AudioUnitRender が呼び出されると、以下の playbackCallback が呼び出されます。これは、最初のノードで入力コールバックとして接続されているものです。PlaybackCallback は循環バッファからデータを取得し、渡された audioBufferList にフィードします。前に述べたように、出力が RemoteIO として設定されている場合、オーディオはスピーカーで正しく再生されます。AudioUnitRender が終了すると、noErr が返され、bufferList オブジェクトには有効なデータが含まれます。 CMSampleBufferSetDataBufferFromAudioBufferList を呼び出すと、 kCMSampleBufferError_RequiredParameterMissing (-12731) が取得されます。
-(CMSampleBufferRef)processOutput
{
if(self.offline == NO)
{
return NULL;
}
AudioUnitRenderActionFlags flags = 0;
AudioTimeStamp inTimeStamp;
memset(&inTimeStamp, 0, sizeof(AudioTimeStamp));
inTimeStamp.mFlags = kAudioTimeStampSampleTimeValid;
UInt32 busNumber = 0;
UInt32 numberFrames = 512;
inTimeStamp.mSampleTime = 0;
UInt32 channelCount = 2;
AudioBufferList *bufferList = (AudioBufferList*)malloc(sizeof(AudioBufferList)+sizeof(AudioBuffer)*(channelCount-1));
bufferList->mNumberBuffers = channelCount;
for (int j=0; j<channelCount; j++)
{
AudioBuffer buffer = {0};
buffer.mNumberChannels = 1;
buffer.mDataByteSize = numberFrames*sizeof(SInt32);
buffer.mData = calloc(numberFrames,sizeof(SInt32));
bufferList->mBuffers[j] = buffer;
}
CheckError(AudioUnitRender(outputUnit, &flags, &inTimeStamp, busNumber, numberFrames, bufferList), @"AudioUnitRender outputUnit");
CMSampleBufferRef sampleBufferRef = NULL;
CMFormatDescriptionRef format = NULL;
CMSampleTimingInfo timing = { CMTimeMake(1, 44100), kCMTimeZero, kCMTimeInvalid };
AudioStreamBasicDescription audioFormat = self.audioFormat;
CheckError(CMAudioFormatDescriptionCreate(kCFAllocatorDefault, &audioFormat, 0, NULL, 0, NULL, NULL, &format), @"CMAudioFormatDescriptionCreate");
CheckError(CMSampleBufferCreate(kCFAllocatorDefault, NULL, false, NULL, NULL, format, numberFrames, 1, &timing, 0, NULL, &sampleBufferRef), @"CMSampleBufferCreate");
CheckError(CMSampleBufferSetDataBufferFromAudioBufferList(sampleBufferRef, kCFAllocatorDefault, kCFAllocatorDefault, 0, bufferList), @"CMSampleBufferSetDataBufferFromAudioBufferList");
return sampleBufferRef;
}
static OSStatus playbackCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
int numberOfChannels = ioData->mBuffers[0].mNumberChannels;
SInt16 *outSample = (SInt16 *)ioData->mBuffers[0].mData;
/
memset(outSample, 0, ioData->mBuffers[0].mDataByteSize);
MyAudioPlayer *p = (__bridge MyAudioPlayer *)inRefCon;
if (p.hasBuffer){
int32_t availableBytes;
SInt16 *bufferTail = TPCircularBufferTail([p getBuffer], &availableBytes);
int32_t requestedBytesSize = inNumberFrames * kUnitSize * numberOfChannels;
int bytesToRead = MIN(availableBytes, requestedBytesSize);
memcpy(outSample, bufferTail, bytesToRead);
TPCircularBufferConsume([p getBuffer], bytesToRead);
if (availableBytes <= requestedBytesSize*2){
[p setReadyForMoreBytes];
}
if (availableBytes <= requestedBytesSize) {
p.hasBuffer = NO;
}
}
return noErr;
}
渡す CMSampleBufferRef は有効に見えます (以下は、デバッガーからのオブジェクトのダンプです)。
CMSampleBuffer 0x7f87d2a03120 retainCount: 1 allocator: 0x103333180
invalid = NO
dataReady = NO
makeDataReadyCallback = 0x0
makeDataReadyRefcon = 0x0
formatDescription = <CMAudioFormatDescription 0x7f87d2a02b20 [0x103333180]> {
mediaType:'soun'
mediaSubType:'lpcm'
mediaSpecific: {
ASBD: {
mSampleRate: 44100.000000
mFormatID: 'lpcm'
mFormatFlags: 0xc2c
mBytesPerPacket: 2
mFramesPerPacket: 1
mBytesPerFrame: 2
mChannelsPerFrame: 1
mBitsPerChannel: 16 }
cookie: {(null)}
ACL: {(null)}
}
extensions: {(null)}
}
sbufToTrackReadiness = 0x0
numSamples = 512
sampleTimingArray[1] = {
{PTS = {0/1 = 0.000}, DTS = {INVALID}, duration = {1/44100 = 0.000}},
}
dataBuffer = 0x0
バッファリストは次のようになります
Printing description of bufferList:
(AudioBufferList *) bufferList = 0x00007f87d280b0a0
Printing description of bufferList->mNumberBuffers:
(UInt32) mNumberBuffers = 2
Printing description of bufferList->mBuffers:
(AudioBuffer [1]) mBuffers = {
[0] = (mNumberChannels = 1, mDataByteSize = 2048, mData = 0x00007f87d3008c00)
}
誰かが助けてくれることを願って、ここで本当に途方に暮れています。ありがとう、
問題がある場合は、これをios 8.3シミュレーターでデバッグしています。オーディオは、iPhone 6で撮影してラップトップに保存したmp4からのものです。
次の問題を読みましたが、まだ役に立たず、機能していません。
AudioBufferList を CMSampleBuffer に変換するには?
AudioBufferList を CMSampleBuffer に変換すると予期しない結果が生じる
CMSampleBufferSetDataBufferFromAudioBufferList がエラー 12731 を返す
アップデート
さらに調べてみると、AudioUnitRender が実行される直前の AudioBufferList が次のようになっていることに気付きました。
bufferList->mNumberBuffers = 2,
bufferList->mBuffers[0].mNumberChannels = 1,
bufferList->mBuffers[0].mDataByteSize = 2048
mDataByteSize は numberFrames*sizeof(SInt32) で、512 * 4 です。playbackCallback で渡された AudioBufferList を見ると、リストは次のようになります。
bufferList->mNumberBuffers = 1,
bufferList->mBuffers[0].mNumberChannels = 1,
bufferList->mBuffers[0].mDataByteSize = 1024
その他のバッファがどこに行くのか、または他の1024バイトサイズが本当にわからない...
もし私がレッドナーに電話をかけ終わったら、もし私がこのようなことをしたら
AudioBufferList newbuff;
newbuff.mNumberBuffers = 1;
newbuff.mBuffers[0] = bufferList->mBuffers[0];
newbuff.mBuffers[0].mDataByteSize = 1024;
newbuff を CMSampleBufferSetDataBufferFromAudioBufferList に渡すと、エラーはなくなります。
BufferList のサイズを 1 mNumberBuffers に設定するか、その mDataByteSize を numberFrames*sizeof(SInt16) に設定しようとすると、AudioUnitRender を呼び出すときに -50 が返されます。
更新 2
スピーカーでサウンドを再生するときに出力を検査できるように、レンダリング コールバックを接続しました。スピーカーに送られる出力にも 2 つのバッファーを持つ AudioBufferList があり、入力コールバック中の mDataByteSize は 1024 で、レンダー コールバックでは 2048 であることに気付きました。これは、AudioUnitRender を手動で呼び出したときに見たものと同じです。レンダリングされた AudioBufferList のデータを調べると、2 つのバッファーのバイトが同じであることがわかります。つまり、2 番目のバッファーは無視できます。しかし、データが取り込まれているため、レンダリング後のサイズが 1024 ではなく 2048 であるという事実を処理する方法がわかりません。なぜそれが起こっているのかについてのアイデアはありますか? オーディオグラフを通過した後、それはより生の形であり、それがサイズが2倍になっている理由ですか?