写点什么

Opus 从入门到精通 (三) 手撸一个 Opus 编码程序

用户头像
轻口味
关注
发布于: 3 小时前
Opus从入门到精通(三)手撸一个Opus编码程序

前面Opus从入门到精通(二):编解码器使用介绍了 opus 编解码器的 API,这边文章介绍编码 API 的具体使用示例.分 Android,ios,Linux 三个系统进行实现.


编码是我们对脉冲编码调制(Pulse Code Modulation,PCM)的数据进行压缩操作,我们通常通过操作系统麦克风 API 获取 PCM 数据,或者从存储的现成的文件的 PCM 数据:


  1. 麦克风回调二进制:- Android 的 AudioRecorder- IOS 的 Audio Unit

  2. 麦克风存储到文件:- Android 的 MediaRecorder- IOS 的 AVFoundation AVAudioRecorder


PCM 数据大小怎么计算呢?根据采样率采样格式,声道数计算.根据前面文章音视频之音频知识入门介绍:PCM 文件大小 = 采样率 * 采样格式 * 声道数 * 录制时长采样率即一秒多少采样,采样格式指一个采用占多少字节,通常一个采用使用一个字节或者两个字节,所以采样率*采样格式计算出一秒钟一个声道 PCM 多少字节,乘以声道数,算出一秒钟 PCM 大小,再乘以时长就可以计算出 PCM 文件大小.


下面分别使用 Android 平台的麦克风二进制回调方式和 IOS 平台的麦克风文件回调方式采集,并使用 OPUS 编码器进行编码.

Android 平台编码程序实现

配置工程

AndroidStudio 新建工程,在见一个 Andorid Library 模块 library,在 library 下面新建类 OpusUtil,并写好 native 方法:


public class OpusUtil {  static {    System.loadLibrary("opusutil-lib");  }  //创建编码器  public static native long _createOpusEncoder(int sampleRateInHz, int channel, int bitrate,      int complexity);  //编码一帧PCM数据  public static native int _encodeOpus(long enc, short[] buffer, int offset, byte[] encoded);  //释放编码器  public static native void _destroyOpusEncoder(long enc);}
复制代码


在 src/main 下面新建 cpp 目录,把从官网下载的 opus 编码器拷贝到 cpp 下,新建 media_jni.c 用于实现 JNI 方法在 library 根目录下创建我们的 CMakeLists.txt,并在 build.gradle 下面配置 cmake 文件:


externalNativeBuild {    cmake {      path file('CMakeLists.txt')    }
复制代码


CMakeLists.txt 将 opus 编码器文件配置好:


# For more information about using CMake with Android Studio, read the# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -s")set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s")
set(libs_include_opus_DIR src/main/cpp/libopus)include_directories( ${libs_include_opus_DIR}/include ${libs_include_opus_DIR}/celt ${libs_include_opus_DIR}/silk ${libs_include_opus_DIR}/silk/float ${libs_include_opus_DIR}/src)
add_library( opusutil-lib
SHARED
src/main/cpp/util.c src/main/cpp/media_jni.c src/main/cpp/jni_utils.c src/main/cpp/libopus/src/opus_multistream_decoder.c src/main/cpp/libopus/src/opus_multistream_encoder.c src/main/cpp/libopus/src/opus_multistream.c src/main/cpp/libopus/src/opus_encoder.c src/main/cpp/libopus/celt/celt_encoder.c src/main/cpp/libopus/celt/bands.c src/main/cpp/libopus/celt/entcode.c src/main/cpp/libopus/celt/entdec.c src/main/cpp/libopus/celt/entenc.c src/main/cpp/libopus/celt/mathops.c src/main/cpp/libopus/celt/vq.c src/main/cpp/libopus/celt/cwrs.c src/main/cpp/libopus/celt/celt.c src/main/cpp/libopus/celt/mdct.c src/main/cpp/libopus/celt/kiss_fft.c src/main/cpp/libopus/celt/bands.c src/main/cpp/libopus/celt/pitch.c src/main/cpp/libopus/celt/celt_lpc.c src/main/cpp/libopus/celt/quant_bands.c src/main/cpp/libopus/celt/laplace.c src/main/cpp/libopus/celt/modes.c src/main/cpp/libopus/celt/rate.c src/main/cpp/libopus/silk/lin2log.c src/main/cpp/libopus/silk/enc_API.c src/main/cpp/libopus/silk/resampler.c src/main/cpp/libopus/silk/resampler_private_IIR_FIR.c src/main/cpp/libopus/silk/resampler_private_up2_HQ.c src/main/cpp/libopus/silk/resampler_private_down_FIR.c src/main/cpp/libopus/silk/resampler_private_AR2.c src/main/cpp/libopus/silk/resampler_rom.c src/main/cpp/libopus/silk/float/encode_frame_FLP.c src/main/cpp/libopus/silk/gain_quant.c src/main/cpp/libopus/silk/log2lin.c src/main/cpp/libopus/silk/encode_pulses.c src/main/cpp/libopus/silk/code_signs.c src/main/cpp/libopus/silk/tables_pulses_per_block.c src/main/cpp/libopus/silk/tables_other.c src/main/cpp/libopus/silk/shell_coder.c src/main/cpp/libopus/silk/encode_indices.c src/main/cpp/libopus/silk/tables_LTP.c src/main/cpp/libopus/silk/tables_pitch_lag.c src/main/cpp/libopus/silk/NLSF_unpack.c src/main/cpp/libopus/silk/tables_gain.c src/main/cpp/libopus/silk/float/wrappers_FLP.c src/main/cpp/libopus/silk/quant_LTP_gains.c src/main/cpp/libopus/silk/VQ_WMat_EC.c src/main/cpp/libopus/silk/NSQ.c src/main/cpp/libopus/silk/LPC_analysis_filter.c src/main/cpp/libopus/silk/NSQ_del_dec.c src/main/cpp/libopus/silk/process_NLSFs.c src/main/cpp/libopus/silk/NLSF2A.c src/main/cpp/libopus/silk/bwexpander_32.c src/main/cpp/libopus/silk/LPC_inv_pred_gain.c src/main/cpp/libopus/silk/table_LSF_cos.c src/main/cpp/libopus/silk/NLSF_encode.c src/main/cpp/libopus/silk/NLSF_decode.c src/main/cpp/libopus/silk/NLSF_stabilize.c src/main/cpp/libopus/silk/sort.c src/main/cpp/libopus/silk/NLSF_VQ_weights_laroia.c src/main/cpp/libopus/silk/NLSF_del_dec_quant.c src/main/cpp/libopus/silk/NLSF_VQ.c src/main/cpp/libopus/silk/interpolate.c src/main/cpp/libopus/silk/float/wrappers_FLP.c src/main/cpp/libopus/silk/A2NLSF.c src/main/cpp/libopus/silk/float/process_gains_FLP.c src/main/cpp/libopus/silk/float/find_pred_coefs_FLP.c src/main/cpp/libopus/silk/float/residual_energy_FLP.c src/main/cpp/libopus/silk/float/energy_FLP.c src/main/cpp/libopus/silk/float/LPC_analysis_filter_FLP.c src/main/cpp/libopus/silk/float/find_LPC_FLP.c src/main/cpp/libopus/silk/float/burg_modified_FLP.c src/main/cpp/libopus/silk/float/inner_product_FLP.c src/main/cpp/libopus/silk/float/scale_copy_vector_FLP.c src/main/cpp/libopus/silk/float/LTP_analysis_filter_FLP.c src/main/cpp/libopus/silk/float/LTP_scale_ctrl_FLP.c src/main/cpp/libopus/silk/float/find_LTP_FLP.c src/main/cpp/libopus/silk/float/scale_vector_FLP.c src/main/cpp/libopus/silk/float/regularize_correlations_FLP.c src/main/cpp/libopus/silk/float/corrMatrix_FLP.c src/main/cpp/libopus/silk/float/noise_shape_analysis_FLP.c src/main/cpp/libopus/silk/float/bwexpander_FLP.c src/main/cpp/libopus/silk/float/LPC_inv_pred_gain_FLP.c src/main/cpp/libopus/silk/float/autocorrelation_FLP.c src/main/cpp/libopus/silk/float/warped_autocorrelation_FLP.c src/main/cpp/libopus/silk/float/apply_sine_window_FLP.c src/main/cpp/libopus/silk/float/find_pitch_lags_FLP.c src/main/cpp/libopus/silk/float/pitch_analysis_core_FLP.c src/main/cpp/libopus/silk/pitch_est_tables.c src/main/cpp/libopus/silk/float/sort_FLP.c src/main/cpp/libopus/silk/resampler_down2.c src/main/cpp/libopus/silk/resampler_down2_3.c src/main/cpp/libopus/silk/float/k2a_FLP.c src/main/cpp/libopus/silk/float/schur_FLP.c src/main/cpp/libopus/silk/LP_variable_cutoff.c src/main/cpp/libopus/silk/biquad_alt.c src/main/cpp/libopus/silk/VAD.c src/main/cpp/libopus/silk/sigm_Q15.c src/main/cpp/libopus/silk/ana_filt_bank_1.c src/main/cpp/libopus/silk/control_SNR.c src/main/cpp/libopus/silk/stereo_encode_pred.c src/main/cpp/libopus/silk/stereo_LR_to_MS.c src/main/cpp/libopus/silk/stereo_quant_pred.c src/main/cpp/libopus/silk/stereo_find_predictor.c src/main/cpp/libopus/silk/inner_prod_aligned.c src/main/cpp/libopus/silk/sum_sqr_shift.c src/main/cpp/libopus/silk/HP_variable_cutoff.c src/main/cpp/libopus/silk/control_codec.c src/main/cpp/libopus/silk/tables_NLSF_CB_NB_MB.c src/main/cpp/libopus/silk/tables_NLSF_CB_WB.c src/main/cpp/libopus/silk/control_audio_bandwidth.c src/main/cpp/libopus/silk/init_encoder.c src/main/cpp/libopus/silk/check_control_input.c src/main/cpp/libopus/src/analysis.c src/main/cpp/libopus/src/repacketizer.c src/main/cpp/libopus/src/opus.c src/main/cpp/libopus/src/opus_decoder.c src/main/cpp/libopus/src/opus_projection_encoder.c src/main/cpp/libopus/src/opus_projection_decoder.c src/main/cpp/libopus/src/mapping_matrix.c src/main/cpp/libopus/src/mapping_matrix.h src/main/cpp/libopus/celt/celt_decoder.c src/main/cpp/libopus/silk/dec_API.c src/main/cpp/libopus/silk/stereo_MS_to_LR.c src/main/cpp/libopus/silk/decode_frame.c src/main/cpp/libopus/silk/PLC.c src/main/cpp/libopus/silk/bwexpander.c src/main/cpp/libopus/silk/CNG.c src/main/cpp/libopus/silk/decode_core.c src/main/cpp/libopus/silk/decode_parameters.c src/main/cpp/libopus/silk/decode_pitch.c src/main/cpp/libopus/silk/decode_pulses.c src/main/cpp/libopus/silk/decode_indices.c src/main/cpp/libopus/silk/stereo_decode_pred.c src/main/cpp/libopus/silk/decoder_set_fs.c src/main/cpp/libopus/silk/init_decoder.c src/main/cpp/libopus/src/mlp.c src/main/cpp/libopus/src/mlp_data.c src/main/cpp/libopus/silk/LPC_fit.c )

find_library( log-lib log)find_library(android-lib android)
target_link_libraries( opusutil-lib ${log-lib} ${android-lib} )add_definitions(-DOUTSIDE_SPEEX -DOPUS_BUILD -DSTDC_HEADERS -DVAR_ARRAYS)
复制代码


最终目录结构如下图:



接下来在 jni 文件下编写编解码实现

实现编码函数

创建编码器:
static jlongcreateOpusEncoder(JNIEnv *env, jobject thiz, jint sampleRateInHz, jint channelConfig, jint bitrate,                  jint complexity) {    int error;    //通过采样率,声道数创建编码器    OpusEncoder *pOpusEnc = opus_encoder_create(sampleRateInHz, channelConfig,                                                OPUS_APPLICATION_RESTRICTED_LOWDELAY,                                                &error);    if (pOpusEnc) {        //设置是否动态码率        opus_encoder_ctl(pOpusEnc, OPUS_SET_VBR(0));//0:CBR, 1:VBR        opus_encoder_ctl(pOpusEnc, OPUS_SET_VBR_CONSTRAINT(true));        //设置码率值(码率是bitspresecond)        opus_encoder_ctl(pOpusEnc, OPUS_SET_BITRATE(bitrate * 1000));        //设置复杂度        opus_encoder_ctl(pOpusEnc, OPUS_SET_COMPLEXITY(complexity));//8    0~10        设置SIGNAl        opus_encoder_ctl(pOpusEnc, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));        opus_encoder_ctl(pOpusEnc, OPUS_SET_LSB_DEPTH(16));        opus_encoder_ctl(pOpusEnc, OPUS_SET_DTX(0));        opus_encoder_ctl(pOpusEnc, OPUS_SET_INBAND_FEC(0));        opus_encoder_ctl(pOpusEnc, OPUS_SET_PACKET_LOSS_PERC(0));    }    return (jlong) pOpusEnc;}
复制代码
编码一帧数据:
//输入short数组的pcm数据samples,输出编码后的byte数组 bytesstatic jint encodeOpus        (JNIEnv *env, jobject thiz, jlong pOpusEnc, jshortArray samples, jint offset,         jbyteArray bytes) {    OpusEncoder *pEnc = (OpusEncoder *) pOpusEnc;    if (!pEnc || !samples || !bytes)        return 0;    jshort *pSamples = (*env)->GetShortArrayElements(env, samples, 0);    jsize nSampleSize = (*env)->GetArrayLength(env, samples);    jbyte *pBytes = (*env)->GetByteArrayElements(env, bytes, 0);    jsize nByteSize = (*env)->GetArrayLength(env, bytes);    if (nSampleSize - offset < 320 || nByteSize <= 0)        return 0;    //编码一帧数据,返回编码完成后的数据大小    int nRet = opus_encode(pEnc, pSamples + offset, nSampleSize, (unsigned char *) pBytes,                           nByteSize);    (*env)->ReleaseShortArrayElements(env, samples, pSamples, 0);    (*env)->ReleaseByteArrayElements(env, bytes, pBytes, 0);    return nRet;}
复制代码
销毁编码器
static void destroyOpusEncoder        (JNIEnv *env, jobject thiz, jlong pOpusEnc) {    OpusEncoder *pEnc = (OpusEncoder *) pOpusEnc;    if (!pEnc)        return;    opus_encoder_destroy(pEnc);}
复制代码


封装编解码函数根据前面减少 API 的文章比较容易实现,采集调用模块有些需要注意事项,下面我们实现

采集模块

利用 Android 的 AudioRecorder 模块读取 PCM 数据,AudioRecorder 对每次读取的内容有最小长度限制,通过 AudioRecord.getMinBufferSize 计算得到.构造一个 Runnable,在构造方法中计算出每次读取最小长度,并且计算出一帧大小,前面文章Opus从入门到精通(二):编解码器使用提到过 OPUS 一帧必须为 2.5ms, 5ms, 10ms, 20ms, 40ms 或 60ms,一帧越小在实时语音中延迟越低,我们取一帧 20ms,计算出一帧大小:


this.opusAudioOpusPath = opusAudioOpusPath;    bufferSize = AudioRecord.getMinBufferSize(Constants.DEFAULT_AUDIO_SAMPLE_RATE,        channelConfig, AudioFormat.ENCODING_PCM_16BIT) + 2048;    audioBuffer = new byte[bufferSize];    audioRecord =        new AudioRecord(MediaRecorder.AudioSource.MIC, Constants.DEFAULT_AUDIO_SAMPLE_RATE,            channelConfig, AudioFormat.ENCODING_PCM_16BIT, bufferSize);    bytesPerTenMS =        Constants.DEFAULT_AUDIO_SAMPLE_RATE * 2 * Constants.DEFAULT_OPUS_CHANNEL / 100 * 2;//每次处理20ms    Log.i(TAG, "bytesPerTenMs:" + bytesPerTenMS);    mRemainBuf = new byte[bytesPerTenMS];    mRemainSize = 0;
复制代码


在 run 方法中我们循环读取麦克风数据,进行编码:


isRecorder = true;    audioRecord.startRecording();    File file = new File(opusAudioOpusPath);    File fileDir = new File(file.getParent());    if (!fileDir.exists()) {      fileDir.mkdirs();    }    if (file.exists()) {      file.delete();    }    long createEncoder = 0;    FileOutputStream fileOutputStream = null;    BufferedOutputStream fileOpusBufferedOutputStream = null;    try {      file.createNewFile();      fileOutputStream = new FileOutputStream(file, true);
fileOpusBufferedOutputStream = new BufferedOutputStream(fileOutputStream);
createEncoder = OpusUtil._createOpusEncoder(Constants.DEFAULT_AUDIO_SAMPLE_RATE, Constants.DEFAULT_OPUS_CHANNEL, 16, 3); Log.i(TAG, "bufferSize:" + bufferSize); while (isRecorder) { int curShortSize = audioRecord.read(audioBuffer, 0, bufferSize); if (curShortSize > 0 && curShortSize <= bufferSize) {
encodeData(createEncoder, fileOpusBufferedOutputStream, curShortSize); } } } catch (IOException e) { e.printStackTrace(); Log.e(TAG, "e = " + e.getMessage()); } finally { OpusUtil._destroyOpusEncoder(createEncoder); audioRecord.stop(); audioRecord.release(); try { if(fileOpusBufferedOutputStream != null) { fileOpusBufferedOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } try { if(fileOutputStream != null) { fileOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } }
复制代码


真正的 encodeData 内容:


private void encodeData(long createEncoder, BufferedOutputStream fileOpusBufferedOutputStream,      int readSize) throws IOException {    byte []data = audioBuffer;    if (mRemainSize > 0) {      byte totalBuf[] = new byte[readSize + mRemainSize];      System.arraycopy(mRemainBuf, 0, totalBuf, 0, mRemainSize);      System.arraycopy(data, 0, totalBuf, mRemainSize, readSize);      data = totalBuf;      readSize += mRemainSize;      mRemainSize = 0;    }    int hasHandleSize = 0;    while (hasHandleSize < readSize) {      int readCount = bytesPerTenMS;      if (bytesPerTenMS > readSize) {        Log.i(TAG, "bytesPerTenMs > readSize");        mRemainSize = readSize;        System.arraycopy(data, 0, mRemainBuf, 0, readSize);        return;      }      if ((readSize - hasHandleSize) < readCount) {        mRemainSize = readSize - hasHandleSize;        Log.d(TAG, "remain size :" + mRemainSize);        System.arraycopy(data, hasHandleSize, mRemainBuf, 0, mRemainSize);        return;      }      byte[] bytes = new byte[readCount];      System.arraycopy(data, hasHandleSize, bytes, 0, readCount);      short[] leftData = ArrayUtil.bytes2shorts(bytes, readCount);      byte[] decodedData = new byte[readCount];      int encodeSize = OpusUtil._encodeOpus(createEncoder, leftData, 0, decodedData);      Log.d(TAG, "encodeSize = " + encodeSize);      if (encodeSize > 0) {        byte[] decodeArray = new byte[encodeSize];        System.arraycopy(decodeArray, 0, decodedData, 0, encodeSize);        fileOpusBufferedOutputStream.write(decodeArray);      } else {        return;      }      hasHandleSize += readCount;    }  }
复制代码


这里面需要注意的是 encodeData 时,因为每次从麦克风读取的数据并不是正好等于一帧,所以我们需要首先判断当前读到的数据是否大于一帧,如果大于一帧则需要循环一帧一帧解码,当循环到最后一次不足一帧时,我们把当前数据缓存起来,和下次从麦克风读取到的数据合并到一起后再进行处理.否则把随意的数据扔到解码器中,解码器会报错.


使用华为荣耀 8 采集,16k 采样,AudioRecord.getMinBufferSize 结果为 1280,加了 2048 后变成了 3328.


源码地址:https://github.com/qingkouwei/AndroidOpusTools

IOS 平台编码程序实现

IOS 平台的编码过程与 Android 大同小异,都是需要注意每帧大小.IOS 使用 AVAudioRecorder 将语音录制成 WAV,然后再通过循环读取 WAV 中 PCM 数据进行编码.


@property (nonatomic, strong) AVAudioSession *audioSession;@property (nonatomic, strong) AVAudioRecorder *audioRecorder;
@implementation AudioManager { dispatch_queue_t queue; void (^_recordProcessHandler)(float volume); void (^_recordCompletedHandler)(NSData *data, NSError *error);}
- (void)recordStartWithProcess:(void (^)(float peakPower))processHandler completed:(void (^)(NSData *data, NSError *error))completedHandler { dispatch_async(queue, ^{ if (!self.audioRecorder.isRecording) { self->_recordProcessHandler = processHandler; self->_recordCompletedHandler = completedHandler; [self.audioRecorder prepareToRecord]; [self.audioRecorder record]; self->_timer.fireDate = [NSDate distantPast]; } else { if (completedHandler) { NSError *error = [NSError errorWithDomain:@"AudioManager" code:-1 userInfo:@{@"info": @"audio recorder is running."}]; dispatch_async(dispatch_get_main_queue(), ^{ completedHandler(nil, error); }); } } });}
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag { if (_recordCompletedHandler) { if (flag) { NSData *data = [[NSData alloc] initWithContentsOfURL:recorder.url]; dispatch_async(dispatch_get_main_queue(), ^{ self->_recordCompletedHandler(data, nil); }); } else { NSError *error = [NSError errorWithDomain:@"AudioManager" code:-2 userInfo:@{@"info": @"audio recorder is failed."}]; dispatch_async(dispatch_get_main_queue(), ^{ self->_recordCompletedHandler(nil, error); }); } }}
复制代码


剩下的主要是如何编译 OPUS 静态库,网上有人写了现成的脚本Opus-iOS


我自己也实现了一个通用的编译 C/C++静态库的脚本:https://github.com/qingkouwei/IOSBuildTools

linux 平台编码程序实现

cmake 工具与 Android 平台类似,将配置改成可执行程序即可:


add_executable(        opustools        ...        )
复制代码


主程序中我们接收两个参数:PCM 文件路径与输出 OPUS 编码后文件路径


int main(int argc, char **argv){    FILE *fin;    FILE *fout;    short *in = NULL;    short *out = NULL;    if (argc != 3)    {        fprintf(stderr, "usage: %s <raw opus input> <mp3 output>\n", argv[0]);        return 1;    }    fin = fopen(argv[1], "rb");    if (!fin)    {        fprintf(stderr, "cannot open input file: %s\n", argv[1]);        return 1;    }    fout = fopen(argv[2], "wb");    if (!fout)    {        fprintf(stderr, "cannot open output file: %s\n", argv[2]);        return 1;    }    ...}
复制代码


创建编码器:


int sampleRateInHz = DEFAULT_SAMPLERATEINHz;    int channelConfig = DEFAULT_CHANNELCONFIG;    int bitrate = DEFAULT_BITRATE;    int error;    OpusEncoder *pOpusEnc = opus_encoder_create(sampleRateInHz, channelConfig,                                                OPUS_APPLICATION_RESTRICTED_LOWDELAY,                                                &error);    if (pOpusEnc) {        opus_encoder_ctl(pOpusEnc, OPUS_SET_VBR(0));//0:CBR, 1:VBR        opus_encoder_ctl(pOpusEnc, OPUS_SET_VBR_CONSTRAINT(true));        opus_encoder_ctl(pOpusEnc, OPUS_SET_BITRATE( bitrate* 1000));        opus_encoder_ctl(pOpusEnc, OPUS_SET_COMPLEXITY(complexity));//8    0~10        opus_encoder_ctl(pOpusEnc, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));        opus_encoder_ctl(pOpusEnc, OPUS_SET_LSB_DEPTH(16));        opus_encoder_ctl(pOpusEnc, OPUS_SET_DTX(0));        opus_encoder_ctl(pOpusEnc, OPUS_SET_INBAND_FEC(0));        opus_encoder_ctl(pOpusEnc, OPUS_SET_PACKET_LOSS_PERC(0));    }
复制代码


循环解码数据:


unsigned char *out = (short *)malloc(READ_BUFFER_SIZE * sizeof(char));    while (1)    {        unsigned char data[READ_BUFFER_SIZE];        num_read = fread(data, 1, READ_BUFFER_SIZE, fin);        short sData[num_read/2];        memcpy(data , sData, num_read );        if (num_read > 0)        {            int result = opus_encode(pOpusEnc, sData, num_read/2, out,                           READ_BUFFER_SIZE * sizeof(char));            if (fwrite(out, 1, result, fout) != (unsigned)(result)){                printf("write error\n",output_samples,result);                goto failure;            }        }        else{            break;        }    }    fclose(fout);    fclose(fin);
复制代码


我们按一帧 20ms 编码,采样率设置为 16k,那么一帧大小 READ_BUFFER_SIZE= 16000 * 2 / 1000 * 20 = 640Byte

用户头像

轻口味

关注

🏆2021年InfoQ写作平台-签约作者 🏆 2017.10.17 加入

Android音视频、AI相关领域从业者

评论

发布
暂无评论
Opus从入门到精通(三)手撸一个Opus编码程序