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

来源:互联网 发布:windows装mac os 编辑:程序博客网 时间:2024/04/27 15:45

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 OKContent-Type: application/jsonCache-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 RequestContent-Type: application/jsonCache-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 1000000using 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_tokenif (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);//设置访问的URLcurl_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的内容如下:





1 0
原创粉丝点击