音频编码之opus

最近项目中用到了语音编码opus,在网上搜了一下,资料非常少,而且没有一个完整的教程,现在简单记录下来opus的使用方法。

首先介绍一下opus

Opus

Opus编码器 是一个有损声音编码的格式,由互联网工程任务组(IETF)进来开发,适用于网络上的实时声音传输,标准格式为RFC 6716。Opus
格式是一个开放格式,使用上没有任何专利或限制。

特性

Opus的前身是celt编码器。在当今的有损音频格式争夺上,拥有众多不同编码器的AAC格式打败了同样颇有潜力的Musepack、Vorbis等格式,而在Opus格式诞生后,情况似乎不同了。通过诸多的对比测试,低码率下Opsu完胜曾经优势明显的HE
AAC,中码率就已经可以媲敌码率高出30%左右的AAC格式,而高码率下更接近原始音频。

以上来自百度百科(PS:百度百科对opus的介绍都很少)

简单来说,opus是一个高保真的适合在网络中传输的开源的语音编码格式,相对于其他编码格式来讲,保真性更好,但体积会稍微大一些。官网地址:http://www.opus-codec.org/

怎么用呢?

首先你可以使用编译好的so库直接使用,或者也可以使用源码自己根据需求生成so库来使用,当然,你也可以直接将源码使用到自己工程各中,这就是开源的好处。好了下面介绍如何编译。

我是通过Eclipse来编译的,首先在opus官网下载源代码,解压。

编码工作需要ndk编程所以需要一些NDK编程的知识。

在工程中创建OpusTool类,该类用于调用native层的方法。

package com.ione.opustool;

public class OpusTool {

	public native String nativeGetString();

	public native int encode_wav_file(String in_path, String out_path);

	public native int decode_opus_file(String in_path, String out_path);
}

其中nativeGetString()方法是用来测试jni调用是否成功的测试方法,encode_wav_file(String in_path, String out_path);和 decode_opus_file(String in_path, String out_path);分别是用来编解码。以上三个方法均需声明为native,用来调用jni的c函数。然后在项目根目录下打开命令行,使用javah命令生成.h文件,即:

javah -classpath .\bin\classes -d jni com.ione.opustool.OpusTool

其中.\bin\classes为指定OpusTool.class的路径,-d jni为在当前目录下生成jni文件夹,用来存放native层代码。回车之后便在工程的根目录下生成了jni文件夹以及com_ione_opustool_OpusTool.h文件。如:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_ione_opustool_OpusTool */

#ifndef _Included_com_ione_opustool_OpusTool
#define _Included_com_ione_opustool_OpusTool
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_ione_opustool_OpusTool
 * Method:    nativeGetString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_ione_opustool_OpusTool_nativeGetString
  (JNIEnv *, jobject);

/*
 * Class:     com_ione_opustool_OpusTool
 * Method:    encode_wav_file
 * Signature: (Ljava/lang/String;Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_encode_1wav_1file
  (JNIEnv *, jobject, jstring, jstring);

/*
 * Class:     com_ione_opustool_OpusTool
 * Method:    decode_opus_file
 * Signature: (Ljava/lang/String;Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_decode_1opus_1file
  (JNIEnv *, jobject, jstring, jstring);

#ifdef __cplusplus
}
#endif
#endif

接下来复制一份com_ione_opustool_OpusTool.h文件到jni目录,修改名称为com_ione_opustool_OpusTool.c修改内容为:

#include <com_ione_opustool_OpusTool.h>

JNIEXPORT jstring JNICALL Java_com_ione_opustool_OpusTool_nativeGetString
                   JNIEnv * env, jobject obj) {
	return (*env)->NewStringUTF(env, "Hello Opus");
}

JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_encode_1wav_1file(
		JNIEnv *env, jobject obj, jstring wav_path, jstring opus_path) {
	return 0;
}

JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_decode_1opus_1file(
		JNIEnv *env, jobject obj, jstring wav_path, jstring opus_path) {
	return 0;
}

然后创建并配置makefile和android.mk文件,后面会给出。记得配置NDK_Builder。

此时可以调用OpusTool类的nativeGetString()方法查看返回数据是否正常,若为Hello Opus 则jni调用成功。可以继续下面的工作。

在jni目录下创建libopus文件夹,将Opus源码粘贴到该文件夹下,即celt、include、silk、src文件夹以及config文件,当然不是所有的文件都用的上,可以根据自记得需求进行拷贝。配置好makefile等配置文件后即可编译工程,如果编译顺利,则说明配置文件没有问题,继续操作。在src文件加下创建opus_tool.c文件用来进行音频文件的编解码的c实现。

opus_tool.c

/*****************************************************************************
 # -*- coding:utf-8 -*-
 # author: ione
 # create date: 2014-11-27
 *****************************************************************************/
#include "android_log.h"
#include "opus.h"
#include "opus_types.h"
#include "opus_multistream.h"

#define SAMPLE_RATE 16000
#define CHANNEL_NUM 1
#define BIT_RATE 16000
#define BIT_PER_SAMPLE 16
#define WB_FRAME_SIZE 320
#define DATA_SIZE 1024 * 1024 * 4

int encode(char* in, int len, unsigned char* opus, int* opus_len) {
	int err = 0;
	opus_int32 skip = 0;

	OpusEncoder *enc = opus_encoder_create(SAMPLE_RATE, CHANNEL_NUM,
			OPUS_APPLICATION_VOIP, &err);
	if (err != OPUS_OK) {
		fprintf(stderr, "cannnot create opus encoder: %s\n",
				opus_strerror(err));
		enc = NULL;
		return -1;
	}

	opus_encoder_ctl(enc, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_WIDEBAND));
	opus_encoder_ctl(enc, OPUS_SET_BITRATE(BIT_RATE));
	opus_encoder_ctl(enc, OPUS_SET_VBR(1));
	opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(10));
	opus_encoder_ctl(enc, OPUS_SET_INBAND_FEC(0));
	opus_encoder_ctl(enc, OPUS_SET_FORCE_CHANNELS(OPUS_AUTO));
	opus_encoder_ctl(enc, OPUS_SET_DTX(0));
	opus_encoder_ctl(enc, OPUS_SET_PACKET_LOSS_PERC(0));
	opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&skip));
	opus_encoder_ctl(enc, OPUS_SET_LSB_DEPTH(16));

	short frame_size = WB_FRAME_SIZE;
	int frame_bytes = (frame_size << 1);

	opus_int16 *frame = (opus_int16 *) in;
	unsigned char *cbits = opus;

	while (len > frame_bytes) {
		int nbytes = opus_encode(enc, frame, frame_size, cbits + sizeof(char),
				640 - sizeof(short));
		if (nbytes > frame_size * 2 || nbytes < 0) {
			return -1;
		}
		cbits[0] = nbytes;
		frame += WB_FRAME_SIZE;
		cbits += nbytes + sizeof(char);
		len -= frame_bytes;
		*opus_len += nbytes + sizeof(char);
	}
	opus_encoder_destroy(enc);
	return 0;
}

int decode(unsigned char* in, int len, short* out, int* out_len) {
	int err = 0;
	opus_int32 skip = 0;
	*out_len = 0;

	OpusDecoder *dec = opus_decoder_create(SAMPLE_RATE, 1, &err);
	if (err != OPUS_OK) {
		fprintf(stderr, "cannnot decode opus: %s\n", opus_strerror(err));
		dec = NULL;
		return -1;
	}

	short frame_size = WB_FRAME_SIZE;

	opus_int16 *frame = (opus_int16 *) in;

	while (len > 0) {
		int nbytes = in[0];
		if (nbytes <= 0) {
			return -1;
		}
		int decode_len = opus_decode(dec, in + sizeof(char), nbytes, out,
				frame_size, 0);
		if (decode_len != frame_size) {
			return -1;
		}

		in += sizeof(char) + nbytes;
		out += frame_size;
		len -= nbytes - sizeof(char);
		*out_len += frame_size;
	}
	opus_decoder_destroy(dec);
	return 0;
}

int encode_wav_file(char *in_file_path, char *out_file_path) {
	FILE *fin = fopen(in_file_path, "rb");

	if (fin == NULL || fin == 0) {
		return -1;
	}
	char *in = (char*) malloc(DATA_SIZE);
	memset(in, 0, DATA_SIZE);
	int len = fread(in, 1, DATA_SIZE, fin);
	if (len == 0) {
		return -1;
	}
	FILE *fout = fopen(out_file_path, "wb");

	if (fout == NULL || fout == 0) {
		return -1;
	}

	unsigned char *out = (unsigned char*) malloc(DATA_SIZE);
	memset(out, 0, DATA_SIZE);
	int out_len = 0;
	encode(in, len, out, &out_len);
	if (len < 0) {
		return -1;
	}
	fwrite(out, 1, out_len * sizeof(unsigned char), fout);

	free(in);
	free(out);
	fclose(fin);
	fclose(fout);
	return len;
}

int make_wav_header(FILE *out, int len) {
	int size = 0;
	int *sz = &size;
	int number;
	int * nm = &number;

	// RIFF  4 bytes
	fseek(out, 0, SEEK_SET);
	fputs("RIFF", out);

	// len   4 bytes
	len = (len + 44 - 8);
	fwrite(&len, 2, 1, out);
	number = 0;
	fwrite(nm, 2, 1, out);

	// WAVE  4 bytes  + "fmt " 4 bytes
	fputs("WAVEfmt ", out);

	// size1   4 bytes
	number = 16;
	fwrite(nm, 2, 1, out);
	number = 0;
	fwrite(nm, 2, 1, out);

	// format tag       2 bytes
	number = 1;
	fwrite(nm, 2, 1, out);

	// channel    2 bytes
	number = CHANNEL_NUM;
	fwrite(nm, 2, 1, out);

	// sample rate          4 bytes
	number = SAMPLE_RATE;
	fwrite(nm, 2, 1, out);
	number = 0;
	fwrite(nm, 2, 1, out);

	//byte per seconds   4 bytes
	number = 22664;
	fwrite(nm, 2, 1, out);
	number = 0;
	fwrite(nm, 2, 1, out);

	// block align   2 bytes
	number = CHANNEL_NUM * BIT_PER_SAMPLE / 8;
	fwrite(nm, 2, 1, out);

	// bitPerSample   2 bytes
	number = 16;
	fwrite(nm, 2, 1, out);

	// "data"      4 bytes
	fputs("data", out);

	// size2    4 bytes
	size = (size - 36);
	fwrite(sz, 2, 1, out);
	number = 0;
	fwrite(nm, 2, 1, out);

	return 0;
}

int decode_opus_file(char *in_file_path, char *out_file_path) {
	printf("%s\n", in_file_path);
	FILE *fin = fopen(in_file_path, "rb");
	if (fin == NULL || fin == 0) {
		return -1;
	}
	unsigned char *in = (unsigned char *) malloc(DATA_SIZE);
	memset(in, 0, DATA_SIZE);
	int len = fread(in, 1, DATA_SIZE, fin);

	FILE *fout = fopen(out_file_path, "wb");
	if (fout == NULL || fout == 0) {
		return -1;
	}
	short *out = (short *) malloc(DATA_SIZE);
	memset(out, 0, DATA_SIZE);

	int out_len = 0;
	out += 44;
	decode(in, len, (short *) out, &out_len);
	if (len < 0) {
		return -1;
	}
	fwrite(out, 1, out_len * sizeof(short), fout);
	int err = make_wav_header(fout, out_len);

	free(in);
	free(out);
	fclose(fin);
	fclose(fout);
	return out_len;
}

配置makefile文件添加opus_tool.c文件,然后编译,即可在libs目录下生成.so文件

至此,native层操作已经完成,so库也已经通过编译得到。

下一篇将会介绍如何使用该so库。

时间: 2024-10-07 08:17:04

音频编码之opus的相关文章

ffmpeg音频编码

在弄音频采集时,需要设置缓存的大小,如果只是简单的采集和直接播放PCM数据,缓存的大小一般不影响播放和保存. 但是,如果需要使用FFMpeg音频编码,这时,音频缓存的大小必须设置av_samples_get_buffer_size函数返回的大小.以下是几点注意的 1. m_pFrame = av_frame_alloc();m_pFrame->format = ffSampleFormat;m_pFrame->nb_samples = nSampleRate;//帧的大小 2. m_nBuff

G.711是一种由国际电信联盟(ITU-T)制定的音频编码方式

http://zh.wikipedia.org/zh-cn/G.711 ITU-T G.711 page ITU-T G.191 software tools for speech and audio coding, including G.711 C code Code Project C# implementation of G.711 with source code G.711是一种由国际电信联盟(ITU-T)制定的音频编码方式,又称为ITU-T G.711. 基本信息编辑 它是国际电信

常用音频协议介绍&amp;&amp;有关音频编码的知识与技术参数

(转载)常用音频协议介绍 会议电视常用音频协议介绍及对比白皮书 一.数字化音频原理:声音其实是一种能量波,因此也有频率和振幅的特征,频率对应于时间轴线,振幅对应于电平轴线.通常人耳可以听到的频率在20Hz到20KHz的声波称为为可听声,低于20Hz的成为次声,高于20KHz的为超声,多媒体技术中只研究可听声部分. 可听声中,话音信号的频段在80Hz到3400Hz之间,音乐信号的频段在20Hz-20kHz之间,语音(话音)和音乐是多媒体技术重点处理的对象. 由于模拟声音在时间上是连续的,麦克风采集

音频编码(转载)

频率 不同频率的正弦波,下部分比上部分频率高频率是单位时间内某事件重复发生次数的度量,在物理学中通常以符号罗马字f 或希腊字ν表示,其国际单位为赫兹(Hz).设 t 时间内某事件重复发生 n 次,则此事件发生的频率为 f = n/t赫兹.又因为周期定义为重复事件发生的最小间隔,故频率也可以周期的倒数表示,即 f = 1/T ,其中 T 表示周期. 60X=n=> x= n/60在国际标准单位里,频率的单位——赫兹,是以海因里希?鲁道夫?赫兹的名字命名.1 赫兹表示事件每一秒发生一次.  每秒一个

有关音频编码的知识与技术参数

会议电视常用音频协议介绍及对比白皮书 一.数字化音频原理:声音其实是一种能量波,因此也有频率和振幅的特征,频率对应于时间轴线,振幅对应于电平轴线.通常人耳可以听到的频率在20Hz到20KHz的声波称为为可听声,低于20Hz的成为次声,高于20KHz的为超声,多媒体技术中只研究可听声部分. 可听声中,话音信号的频段在80Hz到3400Hz之间,音乐信号的频段在20Hz-20kHz之间,语音(话音)和音乐是多媒体技术重点处理的对象. 由于模拟声音在时间上是连续的,麦克风采集的声音信号还需要经过数字化

音频编码介绍汇总

音频编码汇总 PCMU(G.711U) 类型:Audio 制定者:ITU-T 所需频宽:64Kbps(90.4) 特性:PCMU和PCMA都能提供较好的语音质量,但是它们占用的带宽较高,需要64kbps. 优点:语音质量优 缺点:占用的带宽较高 应用领域:voip 版税方式:Free 备注:PCMU and PCMA都能够达到CD音质,但是它们消耗的带宽也最多(64kbps).如果网络带宽比较低,可以选用低比特速率的编码方法,如G.723或G.729,这两种编码的方法也能达到传统长途电话的音质,

iOS平台上音频编码成aac

小程之前介绍解码aac时,曾经使用了fadd,并且有提到,如果想编码成aac格式,可以使用facc.fdk-aac等,但使用fdk-aac等编码方式,都是软编码,在cpu的消耗上会明显大于硬件编码. 硬编码的优势是可以用硬件芯片集成的功能,高速且低功耗地完成编码任务. 在iOS平台,也提供了硬编码的能力,APP开发时只需要调用相应的SDK接口就可以了. 这个SDK接口就是AudioConverter. 本文介绍iOS平台上,如何调用AudioConverter来完成aac的硬编码. 从名字来看,

多媒体开发(13):iOS上音频编码成aac

如前面我所说,对于音频的解码,一般你都不用考虑硬解,用软解就足够了,这时可以选择faad或FFmpeg等.但是,如果是音频的编码呢?这可不一样,编码比解码明显耗时,为了快跟低功耗(特别对于低端机器),要优先考虑硬编码(不能再使用fdk-aac或faac之类的软编码),硬编码的优势是可以用硬件芯片集成的功能,高速且低功耗地完成编码任务. iOS平台,也提供了硬编码的能力,APP开发时只需要调用相应的SDK接口就能达成目标,这个SDK接口就是AudioConverter. 本文介绍iOS平台上,如何

视频参数(流媒体系统,封装格式,视频编码,音频编码,播放器)对比

发现了几个视频参数对比的资源,是Wikipedia上的,总结的非常好: 流媒体系统对比: http://en.wikipedia.org/wiki/Comparison_of_streaming_media_systems 封装格式对比: http://en.wikipedia.org/wiki/Comparison_of_container_formats 视频编码器对比: http://en.wikipedia.org/wiki/Comparison_of_video_codecs 音频编码