2015-05-16 15 views
5

Alle Beispielcode, den ich finden kann, der AudioConverterRef verwendet, konzentriert sich auf Anwendungsfälle, in denen ich alle Daten im Voraus habe (z. B. das Konvertieren einer Datei auf der Festplatte). Sie rufen üblicherweise AudioConverterFillComplexBuffer mit dem PCM an, das als inInputDataProcUserData konvertiert werden soll, und füllen es einfach im Callback ein. (Ist das wirklich so, wie es verwendet werden soll? Warum braucht es dann einen Rückruf?) Für meinen Anwendungsfall versuche ich, aac Audio vom Mikrofon zu streamen, also habe ich keine Datei, und mein PCM-Puffer ist in Echtzeit ausgefüllt.Wie verwende ich den AudioConverter von CoreAudio, um AAC in Echtzeit zu kodieren?

Da ich nicht alle Daten im Voraus habe, habe ich versucht, *ioNumberDataPackets = 0 in den Rückruf zu tun, sobald meine Daten eingegeben sind, aber das bringt den AudioConverter in einen toten Zustand, wo es AudioConverterReset() Ted sein muss und ich bekomme keine Daten daraus.

Ein Ansatz, den ich online vorgeschlagen habe ist, einen Fehler aus dem Rückruf zurückzugeben, wenn die Daten, die ich gespeichert habe, zu klein ist, und versuchen Sie es erneut, sobald ich mehr Daten habe, aber das scheint so eine Verschwendung von Ressourcen Ich kann mich nicht dazu bringen, es überhaupt auszuprobieren.

Muss ich wirklich den "Versuch wiederholen, bis mein Eingangspuffer groß genug ist", oder gibt es einen besseren Weg?

Antwort

12

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]; 
} 
+0

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

0

Für zukünftige Referenz gibt es eine Möglichkeit, viel einfacher.

Der Zustand der CoreAudio- Header:

Wenn der Rückruf einen Fehler zurückgibt, muss er keine Pakete von Daten zurückgeben. AudioConverterFillComplexBuffer hört auf, Ausgabe zu produzieren und gibt zurück, was Ausgabe bereits zu seinem Aufrufer zusammen mit dem Fehlercode produziert wurde.Dieser Mechanismus kann verwendet werden, wenn ein Eingangsprozessor vorübergehend keine Daten mehr hat, aber das Ende des Datenstroms noch nicht erreicht hat.

Also, genau das tun. Anstatt noErr mit * ioNumberDataPackets = 0 zurückzugeben, gebe irgendeinen Fehler zurück (mach einfach einen, ich benutze -1) und die bereits konvertierten Daten werden zurückgegeben, während der Audio Converter am Leben bleibt und nicht zurückgesetzt werden muss.

+0

Ich habe das versucht; Wenn ich diesen Ansatz versuchte, würde AudioConverter mir einen 12-Byte-Puffer mit nur einem mpeg-Header geben und mich dann weigern, mehr Daten zu nehmen. Ich nahm an, dies bedeutet, dass AC genug Daten benötigt, um volle aac-Frames zum Arbeiten zu senden. – nevyn

+0

Ahhh. Es könnte sein. Ich arbeite mit nur PCM-Ausgabe und es funktioniert gut für mich. AudioConverter behält seine eigene interne Pufferung bei, es ist also merkwürdig, dass dies auch für AAC nicht funktionieren würde. Aber die API gibt an, dass sie etwas ausgeben muss, vielleicht bringt sie das an einen seltsamen Ort. –

Verwandte Themen