2015-05-16 13 views
5

Tất cả mã mẫu tôi có thể tìm thấy sử dụng AudioConverterRef tập trung vào các trường hợp sử dụng nơi tôi có tất cả dữ liệu phía trước (chẳng hạn như chuyển đổi tệp trên đĩa). Họ thường gọi AudioConverterFillComplexBuffer với PCM để được chuyển đổi thành inInputDataProcUserData và chỉ cần điền vào trong cuộc gọi lại. Trong trường hợp sử dụng của tôi, tôi đang cố gắng phát âm thanh từ micro, vì vậy tôi không có tập tin nào, và bộ đệm PCM của tôi đang được sử dụng. điền vào trong thời gian thực.Làm cách nào để sử dụng AudioConverter của CoreAudio để mã hóa AAC trong thời gian thực?

Vì tôi không có tất cả dữ liệu ở phía trước, tôi đã thử thực hiện *ioNumberDataPackets = 0 khi gọi lại khi dữ liệu đầu vào của tôi bị hỏng, nhưng chỉ cần đặt AudioConverter ở trạng thái chết ở đó cần phải là AudioConverterReset() ted và tôi không nhận được bất kỳ dữ liệu nào từ đó.

Một cách tiếp cận mà tôi đã thấy được đề xuất trực tuyến là trả lại lỗi từ cuộc gọi lại nếu dữ liệu tôi đã lưu trữ quá nhỏ và chỉ cần thử lại sau khi tôi có nhiều dữ liệu hơn. Tôi không thể tự mình thử nó.

Tôi có thực sự cần thực hiện "thử lại cho đến khi bộ đệm đầu vào của tôi đủ lớn" hay có cách nào tốt hơn không?

Trả lời

12

AudioConverterFillComplexBuffer không thực sự có nghĩa là "lấp đầy bộ mã hóa bằng bộ đệm đầu vào của tôi mà tôi có ở đây". Nó có nghĩa là "lấp đầy bộ đệm đầu ra đầu ra này tại đây với dữ liệu được mã hóa từ bộ mã hóa". Với quan điểm này, gọi lại đột nhiên có ý nghĩa - nó được sử dụng để lấy dữ liệu nguồn để đáp ứng yêu cầu "điền vào bộ đệm đầu ra này cho tôi". Có lẽ điều này là hiển nhiên đối với những người khác, nhưng tôi mất thời gian dài để hiểu điều này (và từ tất cả mã mẫu AudioConverter tôi thấy nổi xung quanh nơi mọi người gửi dữ liệu đầu vào qua inInputDataProcUserData, tôi đoán mình không phải là người duy nhất).

Cuộc gọi AudioConverterFillComplexBuffer đang chặn và mong bạn phân phối dữ liệu đến đồng bộ từ cuộc gọi lại. Nếu bạn mã hóa theo thời gian thực, bạn sẽ cần gọi số FillComplexBuffer trên một chuỗi riêng biệt mà bạn tự thiết lập. Trong cuộc gọi lại, sau đó bạn có thể kiểm tra dữ liệu đầu vào có sẵn và nếu nó không có sẵn, bạn cần phải chặn trên một semaphore. Sử dụng một NSCondition, thread encoder sau đó sẽ giống như thế này:

- (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]; 
    } 
} 

Và gọi lại có thể trông như thế này: (lưu ý rằng tôi thường sử dụng tấm bạt lò xo này ngay lập tức về phía trước đến một phương pháp trên dụ của tôi (bằng cách chuyển tiếp của tôi Ví dụ trong inUserData; bước này được bỏ qua cho ngắn gọn)):

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; 
} 

và cho đầy đủ, đây là cách bạn sau đó sẽ ăn encoder này với dữ liệu, và làm thế nào để tắt nó đúng cách:

- (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

Tôi có một số rắc rối với việc điền iodate với _inputBuffer và đặt ioNumberDataPackets, bạn có thể vui lòng điền mã không? Một số câu hỏi: Chúng ta có cần đặt ioData.mNumberBuffers thành 1 không? Chúng ta có cần điền tất cả dữ liệu từ _inputBuffer vào ioData.mBuffers [0] không? Làm thế nào chúng ta có thể tính toán ioNumberDataPackets? hoặc chỉ cần đặt nó là 1? Ý bạn là "đặt ioNumberDataPackets thành lượng dữ liệu còn lại?" trong khi tài liệu nói "khi thoát, số gói dữ liệu âm thanh thực sự được cung cấp cho đầu vào"? – lancy

0

Để tham khảo trong tương lai, có một cách tùy chọn dễ dàng hơn.

nhà nước Các CoreAudio tiêu đề của:

Nếu gọi lại trả về một lỗi, nó phải trở về zero gói dữ liệu. AudioConverterFillComplexBuffer sẽ ngừng sản xuất đầu ra và trả lại bất kỳ đầu ra nào đã được tạo ra cho người gọi, cùng với mã lỗi.Cơ chế này có thể được sử dụng khi một proc đầu vào tạm thời hết dữ liệu, nhưng vẫn chưa đến cuối luồng.

Vì vậy, hãy làm chính xác điều đó. Thay vì trả về noErr với * ioNumberDataPackets = 0, trả về bất kỳ lỗi nào (chỉ cần tạo một lỗi, tôi đã sử dụng -1) và dữ liệu đã chuyển đổi sẽ được trả về, trong khi Trình chuyển đổi âm thanh vẫn hoạt động và không cần phải đặt lại.

+0

Tôi đã thử điều đó; khi tôi thử phương pháp này, AudioConverter sẽ cho tôi bộ đệm 12 byte chỉ với tiêu đề mpeg và sau đó từ chối lấy thêm dữ liệu. Tôi cho rằng điều này có nghĩa là AC cần đủ dữ liệu để phát ra toàn bộ khung aac để hoạt động. – nevyn

+0

Ahhh. Nó có thể là. Tôi đang làm việc với chỉ PCM đầu ra và nó làm việc tuyệt vời cho tôi. AudioConverter không duy trì nội bộ đệm riêng của nó vì vậy nó lẻ này sẽ không làm việc cho AAC quá. Nhưng API cho biết nó phải xuất ra một thứ gì đó để có thể đặt chúng ở một nơi kỳ lạ. –

Các vấn đề liên quan