REST实战——调用百度语音的云服务

RESTful

REST(REpresentation State Transfer)描述了一个架构样式的网络系统,比如说web应用程序。它首次出现在2000年Roy Thomas Fielding的博士论文中,他是 HTTP 规范的主要编写者之一。REST指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计,即具有REST风格就是RESTful。在REST中,以资源为核心,任何事物,只要具有被引用的必要,就是一个资源。每个资源必须至少有一个统一资源标识符,即URI,URI既是资源的名称也是资源的地址。URI和资源之间的关系是多对一的,也就是说一个URI仅标识一个资源,但是一个资源可以有多余一个URI。REST中的资源是数据和表现形式的组合,以资源为核心的设计思想是REST的核心所在。资源是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的表现层(Representation)。如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。REST规范包括:客户-服务器,无状态,缓存,统一接口,分层系统和按需代码。这些架构约束是从Fielding的博士论文中直译过来的,所以极力推荐阅读Roy Thomas Fielding的博士论文,可以对REST有一个更深刻的理解。

目前,基于RESTful的Web Service在一些领域得到了较好的利用,一些大公司已经提供了基于REST的网络服务。比如说的国外的谷歌、亚马逊等,国内的百度等公司。下面就以一个比较有意思的语音识别云服务为例,该服务是百度http://yuyin.baidu.com/提供的。

百度语音云服务

百度的语音识别服务通过RESTAPI的方式给开发者提供了一个通用的HTTP接口。这么做的一个好处就是轻量级。这里的轻量级的意思是,不需要在开发的应用中集成任何SDK,也不需要在测试机中添加任何的识别引擎。开发者只需要了解HTTP网络请求以及百度语音REST API的使用规则,就可以在自己的应用中实现语音识别功能。

当然,在使用百度的语音云服务之前,肯定是要进行开发者注册申请的,申请完以后百度会给开发者提供一个API Key和Secret Key,在后期跟百度的服务器通信时会用到两个Key。在使用百度的语音识别 REST API之前,需要获取一个Access Token。这个Access Token 是用户身份验证和授权的凭证。百度的语音识别采用的是Client Credentials(http://developer.baidu.com/wiki/index.php?title=docs/oauth/client)授权方式,即采用应用公钥、密钥的方式来获取Access Token,适用于任何带server类型应用,通过此授权方式获取的Access Token仅可访问平台授权类的接口,也就是说,通过它所获取的Access Token只能用于访问与用户无关的Open API。获取Access Token的方法也很简单。开发者按照百度的要求,给百度的服务器发送特定格式的请求数据包,百度的服务器就会返回一个响应数据包,这个响应数据包里面就有Json格式的数据,其中的一个字段就是access_token。

获取Access Token需要应用给百度POST一个OAuth2.0授权服务的请求,POST到的地址为https://openapi.baidu.com/oauth/2.0/token,还需要带上以下的参数:grant_type:必须参数,固定为“client_credentials”;client_id:必须参数,也就是开发者申请时百度给开发者提供的API Key;client_secret:必须参数,也就是开发者申请时百度给开发者提供的Secret Key;scope:非必须参数,是一个以空格分隔的权限列表。比如说一个标准的POST请求数据包的格式如下:

https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=Va5yQRHlA4Fq4eR3LT0vuXV4&client_secret= 0rDSjzQ20XUj5itV7WRtznPQSzr5pVw2

在请求参数无误的情况下,百度的服务器会返回一段Json格式的文本,具有以下字段:access_token:要获取的Access Token;expires_in:Access Token的有效期,以秒为单位;refresh_token:用于刷新Access Token 的Refresh Token,所有应用都会返回该参数;scope:Access Token最终的访问范围,即用户实际授予的权限列表(用户在授权页面时,有可能会取消掉某些请求的权限),关于权限的具体信息参考“权限列表”一节;session_key:基于http调用Open API时所需要的Session Key,其有效期与Access Token一致;session_secret:基于http调用Open API时计算参数签名用的签名密钥。例如,百度服务器返回的一个响应数据包如下:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
 
{
"access_token": "1.a6b7dbd428f731035f771b8d15063f61.86400.1292922000-2346678-124328",
    "expires_in": 86400,
"refresh_token": "2.385d55f8615fdfd9edb7c4b5ebdc3e39.604800.1293440400-2346678-124328",
    "scope": "public",
    "session_key": "ANXxSNjwQDugf8615OnqeikRMu2bKaXCdlLxn",
    "session_secret": "248APxvxjCZ0VEC43EYrvxqaK4oZExMB",
}

若请求错误,百度的服务器也会返回一段Json格式的文本,包含以下参数:error:错误码,即错误类型的代码;error_description:错误描述信息,用来帮助理解和解决发生的错误。比如说如下的请求响应数据包:

HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store
 
{
    "error": "invalid_grant",
"error_description":"Invalid authorization code: ANXxSNjwQDugOnqeikRMu2bKaXCdlLxn"
}

想利用C或者C++语言来完成上述工作时,有一个很强大的工具:curl。curl是利用URL语法在命令行方式下工作的开源文件传输工具。它被广泛应用在Unix、多种Linux发行版中,并且有DOS和Win32、Win64下的移植版本。在C/C++中也有对应的开发库函数——libcurl,该库为开发者提供了丰富的库函数用于给服务器通信。比如说想利用curl获取百度的Access Token,方法也很简单,利用curl –s命令给百度的服务器POST一个请求就可以实现,我在自己的机器上实验如下图:


通过上图,可以看出响应的结果就是一段Json格式的数据包。其中 “access_token” 字段即为请求REST API 所需的令牌,默认情况下,Access Token的有效期是一个月,开发者需要对Access Token的有效性进行判断,如果Access Token过期可以重新获取。
想利用百度的语音识别服务来识别本地的一段语音也很简单,直接给百度的服务器POST自己的语音数据,对方的服务器就会返回一段Json格式的文本,相应的字段就是识别的结果,将这个结果解析出来就可以利用识别的结果了。
百度语音的云服务支持POST的方式来上传语音数据;目前百度语音的REST API仅支持整段语音识别的模式,即需要上传整段语音进行识别。语音数据的压缩格式有以下几种:pcm(不压缩)、wav、opus、speex、amr、x-flac这几种。
语音数据的上传方式有两种:隐示发送和显示发送。所谓的隐式发送就是就是将语音数据格式化成标准的Json格式数据,通过POST上传,这里的Json格式规定了相应的字段,当然,在格式化之前需要将语音数据进行Base64编码。所谓的显示发送顾名思义就是将语音数据直接放在HTTP-BODY中,控制REST参数以及相关的统计信息就可以通过REST API进行传递。对于上面的两种上传方式,百度的服务器都会返回统一的结果,都采用Json格式封装。如果识别成功,识别结果放在 JSON的“result”字段中,统一采用 utf-8 方式编码。
对于上面的所有HTTP动作,在liburl库中都有相应的库函数,所以用起来非常得方便。下面给出一个例子。利用百度的语音识别服务来POST一段本地pcm格式的语音数据给百度的服务器,语音数据的内容是“百度语音提供技术支持”。源码也很简单,主要就是base64编码、Json格式数据解析和利用libcurl库函数来进行POST、GET操作。环境是在Ubuntu下进行的,当然在使用之前需要先安装jsoncpp库和libcurl库,安装方法也很简单,网上有一大堆。这里直接贴上源码:

#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include "curl/curl.h"

#include "curl/easy.h"
#include "json/json.h"
#define MAX_BUFFER_SIZE 512
#define MAX_BODY_SIZE 1000000
using namespace std;

static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static inline bool is_base64(unsigned char c) 
{
	return (isalnum(c) || (c == '+') || (c == '/'));
}

string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) 
{
	std::string ret;
	int i = 0;
	int j = 0;
	unsigned char char_array_3[3];
	unsigned char char_array_4[4];

	while (in_len--) 
	{
		char_array_3[i++] = *(bytes_to_encode++);
		if (i == 3) 
		{
			char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
			char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
			char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
			char_array_4[3] = char_array_3[2] & 0x3f;

			for (i = 0; (i <4); i++)
				ret += base64_chars[char_array_4[i]];
			i = 0;
		}
	}

	if (i)
	{
		for (j = i; j < 3; j++)
			char_array_3[j] = '\0';

		char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
		char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
		char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
		char_array_4[3] = char_array_3[2] & 0x3f;

		for (j = 0; (j < i + 1); j++)
			ret += base64_chars[char_array_4[j]];

		while ((i++ < 3))
			ret += '=';
	}
	return ret;
}

string base64_decode(std::string const& encoded_string) 
{
	int in_len = encoded_string.size();
	int i = 0;
	int j = 0;
	int in_ = 0;
	unsigned char char_array_4[4], char_array_3[3];
	std::string ret;

	while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) 
	{
		char_array_4[i++] = encoded_string[in_]; in_++;
		if (i == 4) 
		{
			for (i = 0; i <4; i++)
				char_array_4[i] = base64_chars.find(char_array_4[i]);

			char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
			char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
			char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

			for (i = 0; (i < 3); i++)
				ret += char_array_3[i];
			i = 0;
		}
	}

	if (i)
	{
		for (j = i; j <4; j++)
			char_array_4[j] = 0;


		for (j = 0; j <4; j++)
			char_array_4[j] = base64_chars.find(char_array_4[j]);

		char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
		char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
		char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

		for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
	}
	return ret;
}

//回调函数
static size_t writefunc(void *ptr, size_t size, size_t nmemb, char **result)
{
	size_t result_len = size * nmemb;
	*result = (char *)realloc(*result, result_len + 1);
	if (*result == NULL)
	{
		printf("realloc failure!\n");
		return 1;
	}
	memcpy(*result, ptr, result_len);
	(*result)[result_len] = '\0';
	cout<<"百度服务器返回的json数据:"<<*result<<endl;
	/*Json::Reader reader;
	Json::Value root;
	if(reader.parse(result,root))
	{
		string res = root["result"].asString();
		cout <<"解析的结果: "<< res << endl;
	}*/
	return result_len;
}

int main()
{
	freopen("out.txt", "w", stdout);
	int json_file_size;
	FILE *pFile = NULL;
	char *audio_data;
	pFile = fopen("test.pcm", "r");
	if (pFile == NULL)
	{
		perror("Open file error!\n");
	}
	else
	{
		fseek(pFile, 0, SEEK_END);
		int file_size = ftell(pFile);
		cout << "file size: " << file_size << " bytes" << endl;
		fseek(pFile, 0, SEEK_SET);
		audio_data = (char *)malloc(sizeof(char)*file_size);
		fread(audio_data, file_size, sizeof(char), pFile);

		//机器的mac地址
		char *cuid = "56:84:7a:fe:97:99";
		char *api_key = "6yFhYifMjXc8QmubiICXBQgi";
		char *secret_key = "nZn45o3X0LGx42qovumYy2mjpOiOup2E";

		char host[MAX_BUFFER_SIZE];
		snprintf(host, sizeof(host),
			"https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s",
			api_key, secret_key);
		cout << "curl -s命令的host: " << host << endl;

		FILE *p = NULL;
		char cmd[MAX_BUFFER_SIZE];
		//curl -s命令的返回结果
		char *result = (char*)malloc(MAX_BUFFER_SIZE);
		char *curl_cmd = "curl -s ";
		char *yinhao = "\"";

		strcpy(cmd, curl_cmd);
		strcat(cmd, yinhao);
		strcat(cmd, host);
		strcat(cmd, yinhao);


		p = popen(cmd, "r");
		fgets(result, MAX_BUFFER_SIZE, p);
		cout << "curl -s 响应结果: " << result << endl;
		pclose(p);

		string access_token;
		//解析服务器返回的Json数据,获取access_token
		if (result != NULL)
		{
			Json::Reader reader;
			Json::Value root;
			if (!reader.parse(result, root, false))
			{
				access_token = root.get("access_token", "").asString();
			}
			cout << "access_token: " << access_token << endl;
		}

		//采取隐式发送的方式给服务器发送json格式的数据
		char body[MAX_BODY_SIZE];
		memset(body, 0, sizeof(body));
		string decode_data = base64_encode((const unsigned char *)audio_data, file_size);
		if (0 == decode_data.length())
		{
			cout << "Error!base64 encoded data is empty!";
			return 1;
		}
		else
		{
			Json::Value buffer;
			Json::FastWriter buf_writer;
			buffer["format"] = "pcm";
			buffer["rate"] = 8000;
			buffer["channel"] = 1;
			buffer["token"] = access_token.c_str();
			buffer["cuid"] = cuid;
			buffer["speech"] = decode_data;
			buffer["len"] = file_size;
			//实际json格式数据的长度
			json_file_size = buf_writer.write(buffer).length();
			cout << "Json file size:" << json_file_size << " bytes" << endl;
			memcpy(body, buf_writer.write(buffer).c_str(), json_file_size);

			CURL *curl;
			CURLcode res;//服务器的响应结果
			char *result_buffer = NULL;
			struct curl_slist *http_header = NULL;
			char temp[MAX_BUFFER_SIZE];
			memset(temp, 0, sizeof(temp));
			snprintf(temp, sizeof(temp), "%s", "Content-Type: application/json; charset=utf-8");
			http_header = curl_slist_append(http_header, temp);
			snprintf(temp, sizeof(temp), "Content-Length: %d", json_file_size);
			http_header = curl_slist_append(http_header, temp);


			memset(host, 0, sizeof(host));
			snprintf(host, sizeof(host), "%s", "http://vop.baidu.com/server_api");
			cout << "server host: " << host << endl;
			curl = curl_easy_init();
			curl_easy_setopt(curl, CURLOPT_URL, host);//设置访问的URL
			curl_easy_setopt(curl, CURLOPT_POST, 1);//1表示常规的http post请求
			curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30);//设置延时
			curl_easy_setopt(curl, CURLOPT_HTTPHEADER, http_header);
			curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
			curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, json_file_size);
			curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);//设置回调函数
			curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result_buffer);
			res = curl_easy_perform(curl);
			if (res != CURLE_OK)
			{
				printf("perform curl error:%d.\n", res);
				return 1;
			}
			curl_slist_free_all(http_header);
			curl_easy_cleanup(curl);
			
			free(audio_data);
		}
	}

	fclose(pFile);
	return 0;

}

因为利用了额外的库函数,所以在编译时需要加上链接参数,编译命令如下:

g++-o bdvoice BaiduVR.cpp -L . -l json_linux-gcc-4.8_libmt -l curl

注:这里将jsoncpp库的头文件在json文件夹下和libcurl库的头文件在curl文件夹下,以及相应的.a和.so库跟代码文件放在一个目录下。运行可执行文件,源码中将标准输出写入到out.txt,out.txt的内容如下:





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值