Ios 如何使用opus对音频进行编码和解码

Ios 如何使用opus对音频进行编码和解码,ios,audio,codec,opus,Ios,Audio,Codec,Opus,我正在尝试将opus集成到我的应用程序中,编码和解码功能返回正值,这意味着成功,但输出音频无法播放。原始音频数据也可以播放。 下面是我如何编码数据。我使用4字节前缀来分隔每个数据包 self.encoder = opus_encoder_create(24000, 1, OPUS_APPLICATION_VOIP, &opusError); opus_encoder_ctl(self.encoder, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_SUPERWIDE

我正在尝试将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.encoder=opus\u encoder\u create(24000,1,opus\u应用程序\u VOIP和opusError);
opus_编码器_ctl(自编码器、opus_设置_带宽(opus_带宽_超宽带));
-(void)encodeBufferList:(AudioBufferList*)bufferList{
BOOL success=TPCircularBufferProduceBytes(_circularBuffer,bufferList->mBuffers[0]。mData,bufferList->mBuffers[0]。mDataByteSize);
如果(!成功){
NSLog(@“循环缓冲区中的空间不足!”);
}
if(!\u编码){
_编码=是;
dispatch\u async(self.processingQueue^{
[self startEncodingLoop];
});
}
}
-(无效)startEncodingLoop
{
int32_t availableBytes=0;
opus_int16*数据=(opus_int16*)TPCircularBufferTail(_circularBuffer,&availableBytes);
int availableSamples=availableBytes/_inputASBD.mBytesPerFrame;
/*!
*使用动态持续时间
*/
//int validSamples[6]={2.5,5,10,20,40,60};//以毫秒为单位
//int-esample=validSamples[0]*self.sampleRate/1000;
//对于(int i=0;i<6;i++){
//int32_t samp=有效样本[i]*自采样器/1000;
//如果(可用样本if(returnValue我注意到的一件事是,当它是压缩数据包中的字节数时,您将的返回值视为编码的样本数。这意味着您正在将_encoderOutputBuffer末尾的50%或75%垃圾数据写入编码流


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

我认为问题出在解码端:

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

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

  • OPUS\u SET\u信号
    在OPUS\u解码器\u ctl()中不起任何作用,但它只会返回未实现的OPUS\u,而不会影响行为

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


我发现了问题所在。我已将音频格式设置为float
kaudioformatflagspacked | kaudioformatflagsfloat;
。我应该使用
opus\u encode\u float
opus\u decode\u float
而不是
opus\u encode

正如@Ralph所说,我们应该在
opus\u decode
中使用
fec
=0。多亏了@Ralph.

\u encoderOutputBuffer
可以容纳1000字节。我在编码之前分配它:
self.encoderOutputBuffer=malloc(1000*sizeof(无符号字符))
你能解释一下为什么我把
returnValue
当作编码的样本数吗?在控制台中,它会打印出27~30个字节。你说得对,对不起。我把
*sizeof(unsigned char)
误读为
*sizeof(opus\u int16)
。感谢您的帮助。
fec
应该是
0
。我已经更改了它。我发现了问题所在,我已将音频输出设置为浮点,但使用
opus\u编码
opus\u解码
。更改为
opus\u编码
opus\u解码
解决了我的问题。是否有一个示例项目我可以看到此代码如何工作?仅在我们的项目中:d@sahara108我也面临类似的问题,你找到解决办法了吗?@Aslam是的。我在本页末尾发布了我的答案。你可以查看。@sahara108我对此有点怀疑。你能分享你的skype id或邮件id吗?这样我就可以解释我的问题了??
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];
}