AudioConverterFillComplexBuffer
bedeutet eigentlich nicht "füllen Sie den Encoder mit meinem Eingangspuffer, die ich hier habe". Es bedeutet, "füllen Sie diesen Ausgabepuffer hier mit kodierten Daten aus dem Encoder". Mit dieser Perspektive macht der Rückruf plötzlich Sinn - er wird verwendet, um Quelldaten zu holen, um die Anfrage "Diesen Ausgangspuffer für mich füllen" zu erfüllen. Vielleicht ist das für andere offensichtlich, aber es dauerte eine lange Zeit, um dies zu verstehen (und von allen AudioConverter Beispielcode ich schweben herum, wo Menschen Eingabedaten über inInputDataProcUserData
senden, ich denke, ich bin nicht der einzige).
Der Anruf AudioConverterFillComplexBuffer
blockiert und erwartet, dass Sie Daten synchron vom Rückruf an ihn senden. Wenn Sie in Echtzeit codieren, müssen Sie daher FillComplexBuffer
in einem separaten Thread aufrufen, den Sie selbst eingerichtet haben. Im Callback können Sie dann nach verfügbaren Eingabedaten suchen. Wenn diese nicht verfügbar sind, müssen Sie einen Semaphor blockieren. Mit Hilfe eines NSCondition würde der Encoder Faden dann in etwa so aussehen:
- (void)startEncoder
{
OSStatus creationStatus = AudioConverterNew(&_fromFormat, &_toFormat, &_converter);
_running = YES;
_condition = [[NSCondition alloc] init];
[self performSelectorInBackground:@selector(_encoderThread) withObject:nil];
}
- (void)_encoderThread
{
while(_running) {
// Make quarter-second buffers.
size_t bufferSize = (_outputBitrate/8) * 0.25;
NSMutableData *outAudioBuffer = [NSMutableData dataWithLength:bufferSize];
AudioBufferList outAudioBufferList;
outAudioBufferList.mNumberBuffers = 1;
outAudioBufferList.mBuffers[0].mNumberChannels = _toFormat.mChannelsPerFrame;
outAudioBufferList.mBuffers[0].mDataByteSize = (UInt32)bufferSize;
outAudioBufferList.mBuffers[0].mData = [outAudioBuffer mutableBytes];
UInt32 ioOutputDataPacketSize = 1;
_currentPresentationTime = kCMTimeInvalid; // you need to fill this in during FillComplexBuffer
const OSStatus conversionResult = AudioConverterFillComplexBuffer(_converter, FillBufferTrampoline, (__bridge void*)self, &ioOutputDataPacketSize, &outAudioBufferList, NULL);
// here I convert the AudioBufferList into a CMSampleBuffer, which I've omitted for brevity.
// Ping me if you need it.
[self.delegate encoder:self encodedSampleBuffer:outSampleBuffer];
}
}
Und der Rückruf könnte wie folgt aussehen: (beachten Sie, dass ich normalerweise dieses Trampolin verwenden, um sofort nach vorne auf ein Verfahren auf meinem Beispiel (durch Weiterleitung meiner Instanz in inUserData
; dieser Schritt der Kürze halber weggelassen)):
static OSStatus FillBufferTrampoline(AudioConverterRef inAudioConverter,
UInt32* ioNumberDataPackets,
AudioBufferList* ioData,
AudioStreamPacketDescription** outDataPacketDescription,
void* inUserData)
{
[_condition lock];
UInt32 countOfPacketsWritten = 0;
while (true) {
// If the condition fires and we have shut down the encoder, just pretend like we have written 0 bytes and are done.
if(!_running) break;
// Out of input data? Wait on the condition.
if(_inputBuffer.length == 0) {
[_condition wait];
continue;
}
// We have data! Fill ioData from your _inputBuffer here.
// Also save the input buffer's start presentationTime here.
// Exit out of the loop, since we're done waiting for data
break;
}
[_condition unlock];
// 2. Set ioNumberDataPackets to the amount of data remaining
// if running is false, this will be 0, indicating EndOfStream
*ioNumberDataPackets = countOfPacketsWritten;
return noErr;
}
und der Vollständigkeit halber hier, wie Sie dann diesen Geber mit Daten füttern würde, und wie es schließen richtig nach unten:
- (void)appendSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
[_condition lock];
// Convert sampleBuffer and put it into _inputBuffer here
[_condition broadcast];
[_condition unlock];
}
- (void)stopEncoding
{
[_condition lock];
_running = NO;
[_condition broadcast];
[_condition unlock];
}
Ich habe einige Probleme mit der Füllung Iodat mit _inputBuffer und setzen ioNumberDataPackets, könnten Sie bitte den Code füllen? Einige Fragen: Müssen wir ioData.mNumberBuffers auf 1 setzen? Müssen wir alle Daten von _inputBuffer zu ioData.mBuffers [0] füllen? Wie können wir die ioNumberDataPackets berechnen? oder einfach auf 1 setzen? Was meinst du "Set ioNumberDataPackets auf die Menge der verbleibenden Daten?" während das Dokument sagt "beim Beenden, die Anzahl der Pakete von Audiodaten, die tatsächlich zur Eingabe bereitgestellt werden"? – lancy