【问题标题】:How to encode and decode audio using opus如何使用 opus 对音频进行编码和解码
【发布时间】:2015-08-07 12:07:18
【问题描述】:

我正在尝试将opus集成到我的应用程序中,编码和解码函数返回正值,表示成功,但输出音频无法播放。原始音频数据也可以播放。 这是我对数据进行编码的方式。我使用 4 字节前缀与每个数据包分开。

self.encoder = opus_encoder_create(24000, 1, OPUS_APPLICATION_VOIP, &opusError);
opus_encoder_ctl(self.encoder, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_SUPERWIDEBAND));



- (void) encodeBufferList:(AudioBufferList *)bufferList {
    BOOL success = TPCircularBufferProduceBytes(_circularBuffer, bufferList->mBuffers[0].mData, bufferList->mBuffers[0].mDataByteSize);
    if (!success) {
        NSLog(@"insufficient space in circular buffer!");
    }

    if (!_encoding) {
            _encoding = YES;

            dispatch_async(self.processingQueue, ^{
                [self startEncodingLoop];
            });
    }
}


-(void)startEncodingLoop
{
    int32_t availableBytes = 0;
    opus_int16 *data = (opus_int16*)TPCircularBufferTail(_circularBuffer, &availableBytes);
    int availableSamples = availableBytes / _inputASBD.mBytesPerFrame;

    /*!
     *  Use dynamic duration
     */
//    int validSamples[6] = {2.5, 5, 10, 20, 40, 60}; // in milisecond
//    int esample = validSamples[0] * self.sampleRate / 1000;
//    for (int i = 0; i < 6; i++) {
//        int32_t samp = validSamples[i] * self.sampleRate / 1000;
//        if (availableSamples < samp) {
//            break;
//        }
//        esample = samp;
//    }

    /*!
     *  Use 20ms
     */
    int esample = 20 * self.sampleRate / 1000;

    if (availableSamples < esample) {
        /*!
         *  Out of data. Finish encoding
         */
        self.encoding = NO;
        [self.eDelegate didFinishEncode];
        return;
    }

//    printf("raw input value for packet \n");
//    for (int i = 0; i < esample * self.numberOfChannels; i++) {
//        printf("%d :", data[i]);
//    }

    int returnValue = opus_encode(_encoder, data, esample, _encoderOutputBuffer, 1000);

    TPCircularBufferConsume(_circularBuffer, esample * sizeof(opus_int16) * self.numberOfChannels);

//    printf("output encode \n");
//    for (int i = 0; i < returnValue; i++) {
//        printf("%d :", _encoderOutputBuffer[i]);
//    }

    NSMutableData *outputData = [NSMutableData new];
    NSError *error = nil;
    if (returnValue <= 0) {
        error = [OKUtilities errorForOpusErrorCode:returnValue];
    }else {
        [outputData appendBytes:_encoderOutputBuffer length:returnValue * sizeof(unsigned char)];
        unsigned char int_field[4];
        int_to_char(returnValue , int_field);
        NSData *header = [NSData dataWithBytes:&int_field[0] length:4 * sizeof(unsigned char)];
        if (self.eDelegate) {
            [self.eDelegate didEncodeWithData:header];
        }
    }

    if (self.eDelegate) {
        [self.eDelegate didEncodeWithData:outputData];
    }

    [self startEncodingLoop];
}

这里是解码函数:

self.decoder = opus_decoder_create(24000, 1, &opusError);
opus_decoder_ctl(self.decoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));
opus_decoder_ctl(self.decoder, OPUS_SET_GAIN(10));


-(void)startParseData:(unsigned char*)data remainingLen:(int)len
{
    if (len <= 0) {
        [self.dDelegate didFinishDecode];
        return;
    }
    int headLen = sizeof(unsigned char) * 4;
    unsigned char h[4];
    h[0] = data[0];
    h[1] = data[1];
    h[2] = data[2];
    h[3] = data[3];

    int packetLen = char_to_int(h);
    data += headLen;
    packetLen = packetLen * sizeof(unsigned char) * self.numberOfChannels;
    [self decodePacket:data length:packetLen remainingLen:len - headLen];
}

-(void)decodePacket:(unsigned char*)inputData length:(int)len remainingLen:(int)rl
{
    int bw = opus_packet_get_bandwidth(inputData); //TEST: return OPUS_BANDWIDTH_SUPERWIDEBAND here
    int32_t decodedSamples = 0;

//    int validSamples[6] = {2.5, 5, 10, 20, 40, 60}; // in milisecond
    /*!
     *  Use 60ms
     */
    int esample = 60 * self.sampleRate / 1000;
//    printf("input decode \n");
//    for (int i = 0; i < len; i++) {
//        printf("%d :", inputData[i]);
//    }

    _decoderBufferLength = esample * self.numberOfChannels * sizeof(opus_int16);
    int returnValue = opus_decode(_decoder, inputData, len, _outputBuffer, esample, 1);
    if (returnValue < 0) {
        NSError *error = [OKUtilities errorForOpusErrorCode:returnValue];
        NSLog(@"decode error %@", error);
        inputData += len;
        [self startParseData:inputData remainingLen:rl - len];
        return;
    }
    decodedSamples = returnValue;

    NSUInteger length = decodedSamples * self.numberOfChannels;

//    printf("raw decoded data \n");
//    for (int i = 0; i < length; i++) {
//        printf("%d :", _outputBuffer[i]);
//    }

    NSData *audioData = [NSData dataWithBytes:_outputBuffer length:length * sizeof(opus_int16)];
    if (self.dDelegate) {
        [self.dDelegate didDecodeData:audioData];
    }
    inputData += len;
    [self startParseData:inputData remainingLen:rl - len];
}

请帮助我指出我所缺少的。一个例子会很棒。

【问题讨论】:

  • 是否有一个示例项目可以让我看到这段代码的功能?
  • 仅在我们的项目中:d
  • @sahara108 iam 也面临类似问题您找到解决方案了吗?
  • @Aslam 是的。我在本页末尾发布了我的答案。你可以检查一下。
  • @sahara108 我对此有点怀疑,你能分享你的 Skype id 或邮件 id..以便我可以解释我的问题吗??

标签: ios audio codec opus


【解决方案1】:

我发现了问题所在。我已将音频格式设置为 float kAudioFormatFlagIsPacked|kAudioFormatFlagIsFloat;。我应该使用opus_encode_floatopus_decode_float 而不是opus_encode opus_decode。 正如@Ralph 所说,我们应该在opus_decode 中使用fec = 0。感谢@Ralph。

【讨论】:

    【解决方案2】:

    我认为问题出在解码端:

    • 您将1 作为fec 参数传递给opus_decode()。这要求解码器从当前数据包中的纠错数据生成完整数据包持续时间的数据。我在您的代码中没有看到任何丢失的数据包跟踪,因此应该传递 0。随着这种变化,您的输入和输出持续时间应该匹配。

    • 您将解码器配置为单声道输出,但稍后在长度计算中使用self.numberOfChannels。这些应该匹配,否则您可能会遇到意外行为。

    • OPUS_SET_SIGNAL 在 opus_decoder_ctl() 中没有做任何事情,但它只会返回 OPUS_UNIMPLEMENTED 而不会影响行为。

    • Opus 数据包的持续时间最长可达 120 毫秒,因此您的 60 毫秒限制可能无法解码某些流。如果您只与您自己的应用程序对话,那么您配置它的方式不会导致问题,因为 libopus 默认为 20 毫秒帧。

    【讨论】:

    • 感谢您的帮助。 fec 应该是 0。我已经改变了。我发现问题出在哪里,我将音频输出设置为浮动但使用opus_encodeopus_decode。更改为opus_encode_floatopus_decode_float 解决我的问题。
    【解决方案3】:

    我注意到的一件事是,当它是压缩数据包中的字节数时,您将 opus_encode() 的返回值视为编码的样本数。这意味着您将 _encoderOutputBuffer 末尾的 50% 或 75% 垃圾数据写入编码流。

    还要确保 _encoderOutputBuffer 有空间容纳您传入的硬编码的 1000 字节数据包长度限制。

    【讨论】:

    • _encoderOutputBuffer 有 1000 个字节的空间。我在编码之前分配它:self.encoderOutputBuffer = malloc(1000 * sizeof(unsigned char)); 你能解释一下为什么我将returnValue 视为编码的样本数吗?在控制台中打印出 27 ~ 30 个字节。
    • 你是对的,对不起。我将* sizeof(unsigned char) 误读为* sizeof(opus_int16)
    猜你喜欢
    • 2019-01-09
    • 2012-11-19
    • 2013-05-05
    • 2020-01-07
    • 2019-09-05
    • 2013-12-26
    • 1970-01-01
    • 2020-09-24
    • 2014-12-02
    相关资源
    最近更新 更多