FreeSWITCH中如何播放阿里OSS上的文件,或录音到OSS上?
在基于freeswitch进行业务开发时,一般只需要播放本地音频文件即可,如果音频文件存储在云端,比如OSS上,亦可下载到本地进行播放。但是,如果云端的音频文件内容变更了,或者业务逻辑变更上传了新的音频文件,这些都需要业务端进行同步。如果有多台freeswitch服务器,则同步操作将会比较繁琐。那有没有简单的方法呢?显然是有的。
freeswitch提供了多样的接口,我们可以定制开发一个模块,实现简单的oss音频文件的下载和播放,而且可以实现便下载边播放。具体操作如下:
1.去阿里下载和安装oss sdk
按如下步骤下载安装即可。
https://help.aliyun.com/document_detail/32132.html?spm=a2c4g.11186623.6.1135.711c3470A1oKJT
2.新建模块
在src/mod/application目录下新建一个模块 mod_ali_oss, 新建代码文件: mod_ali_oss.c,
引入头文件:
1 #include <switch.h> 2 #include <switch_curl.h> 3 #include <stdlib.h> 4 5 #include <aos_util.h> 6 #include <aos_string.h> 7 #include <aos_status.h> 8 #include <oss_auth.h> 9 #include <oss_api.h>
定义模块入口:
1 SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_ali_oss_shutdown); 2 SWITCH_MODULE_LOAD_FUNCTION(mod_ali_oss_load); 3 SWITCH_MODULE_DEFINITION(mod_ali_oss, mod_ali_oss_load, mod_ali_oss_shutdown, NULL);
模块加载时函数入口:
1 SWITCH_MODULE_LOAD_FUNCTION(mod_ali_oss_load) 2 { 3 switch_file_interface_t* file_interface; 4 *module_interface = switch_loadable_module_create_module_interface(pool, modname); 5 6 switch_core_new_memory_pool(&pool); 7 8 memset(&globals, 0, sizeof(oss_global_t)); 9 globals.pool = pool; 10 // 存储已经缓存到本端的OSS文件. 11 switch_core_hash_init(&globals.map_cache); 12 switch_mutex_init(&globals.map_mutex, SWITCH_MUTEX_DEFAULT, globals.pool); 13 14 if (do_config(&globals) != SWITCH_STATUS_SUCCESS) { 15 switch_core_destroy_memory_pool(&globals.pool); 16 return SWITCH_STATUS_FALSE; 17 } 18 19 // 初始化oss sdk 20 if (aos_http_io_initialize(NULL, 0) != AOSE_OK) { 21 switch_core_destroy_memory_pool(&globals.pool); 22 return SWITCH_STATUS_FALSE; 23 } 24 25 // 创建文件操作的接口 26 file_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_FILE_INTERFACE); 27 file_interface->interface_name = "oss"; // 文件操作的前缀 28 file_interface->extens = oss_supported_formats; 29 file_interface->file_open = oss_file_open; 30 file_interface->file_close = oss_file_close; 31 file_interface->file_read = oss_file_read; 32 file_interface->file_write = oss_file_write; 33 ...
其中核心是 switch_loadable_module_create_interface 函数, 向freeswitch注册创建一个文件接口.
file_interface->interface_name = "oss"; 注册文件接口名称, 我们使用oss作为接口名称
在模块被加载到freeswitch中后, 我们可以这样播放oss文件:
1 <action application="playback" data="oss://xxxx.oss-cn-hangzhou.aliyuncs.com/myfile.wav" />
也可以这样录音到oss:
1 <action application="record_session" data="oss://xxx.oss-cn-hangzhou.aliyuncs.com/record/rec001.wav" />
其中xxx是桶(bucket)的名称, 至于访问oss需要的AK,SK则由从配置文件读入.
读取配置:
1 switch_status_t do_config(oss_global_t *cache) 2 { 3 char *cf = "ali_oss.conf"; 4 switch_xml_t cfg, xml, param, settings; 5 switch_status_t status = SWITCH_STATUS_SUCCESS; 6 7 if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL))) { 8 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", cf); 9 return SWITCH_STATUS_TERM; 10 } 11 12 // 从文件获取配置. 13 settings = switch_xml_child(cfg, "settings"); 14 if (settings) 15 { 16 for (param = switch_xml_child(settings, "param"); param; param = param->next) { 17 char *var = (char *) switch_xml_attr_soft(param, "name"); 18 char *val = (char *) switch_xml_attr_soft(param, "value"); 19 20 if (!strcasecmp(var, "oss-access-key-id")) { 21 cache->oss_access_key_id = switch_core_strdup(cache->pool, val); 22 } 23 else if (!strcasecmp(var, "oss-secret-access-key")) { 24 cache->oss_secret_access_key = switch_core_strdup(cache->pool, val); 25 } 26 else if (!strcasecmp(var, "oss-bucket")) 27 { 28 cache->oss_bucket = switch_core_strdup(cache->pool, val); 29 } 30 else if (!strcasecmp(var, "oss-endpoint")) 31 { 32 cache->oss_endpoint = switch_core_strdup(cache->pool, val); 33 } 34 } 35 } 36 37 switch_xml_free(xml); 38 39 // 如果缺少必要参数,则返回失败. 40 if (NULL == cache->oss_access_key_id || NULL == cache->oss_secret_access_key || NULL == cache->oss_bucket || 41 NULL == cache->oss_endpoint) { 42 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "missing parameter\n"); 43 return SWITCH_STATUS_TERM; 44 } 45 46 return status; 47 }
打开文件的操作:
1 switch_status_t oss_file_open(switch_file_handle_t *handle, const char *object) 2 { 3 if (switch_test_flag(handle, SWITCH_FILE_FLAG_WRITE)) 4 { 5 return oss_file_open_write(handle, object); 6 } 7 else 8 { 9 return oss_file_open_read(handle, object); 10 } 11 }
打开文件读(放音):
1 switch_status_t oss_file_open_read(switch_file_handle_t *handle, const char *object) 2 { 3 oss_playback_context_t *context = NULL; 4 oss_cache_context_t *cc; 5 switch_status_t status = SWITCH_STATUS_SUCCESS; 6 7 switch_mutex_lock(globals.map_mutex); 8 cc = switch_core_hash_find(globals.map_cache, object); 9 if (NULL == cc) 10 { 11 cc = oss_add_cache(object); 12 } 13 14 if (cc) 15 { 16 cc->recent = time(NULL); 17 } 18 switch_mutex_unlock(globals.map_mutex); 19 20 if (NULL == cc) { return SWITCH_STATUS_FALSE; } 21 22 // 播放的通道. 23 context = switch_core_alloc(handle->memory_pool, sizeof(oss_playback_context_t)); 24 25 if (OSS_FILE_READY == cc->status) { 26 27 // 直接打开返回. 28 context->fh.pre_buffer_datalen = handle->pre_buffer_datalen; 29 status = switch_core_file_open(&context->fh, cc->local_path, handle->channels, handle->samplerate, 30 SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT, NULL); 31 32 if (SWITCH_STATUS_SUCCESS != status) { 33 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to open cache file: %s, %s\n", 34 cc->local_path, object); 35 return status; 36 } 37 38 // fh 有效的情况下, cache设置位NULL. 39 context->cache = NULL; 40 handle->private_info = context; 41 42 handle->samples = context->fh.samples; 43 handle->format = context->fh.format; 44 handle->sections = context->fh.sections; 45 handle->seekable = context->fh.seekable; 46 handle->speed = context->fh.speed; 47 handle->interval = context->fh.interval; 48 handle->channels = context->fh.channels; 49 handle->flags |= SWITCH_FILE_NOMUX; 50 handle->pre_buffer_datalen = 0; 51 52 if (switch_test_flag((&context->fh), SWITCH_FILE_NATIVE)) { 53 switch_set_flag_locked(handle, SWITCH_FILE_NATIVE); 54 } else { 55 switch_clear_flag_locked(handle, SWITCH_FILE_NATIVE); 56 } 57 58 // switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "open cache file: %s, %s\n", cc->local_path, 59 // object); 60 61 return status; 62 } 63 64 // fh 无效的情况下, cache设置为 cc 65 context->cache = cc; 66 handle->private_info = context; 67 handle->samplerate = 8000; 68 handle->channels = 1; 69 handle->format = 0; // SF_FORMAT_RAW | SF_FORMAT_PCM_16;// config.format; 70 handle->seekable = 0; 71 handle->speed = 0; 72 handle->pre_buffer_datalen = 0; 73 74 // switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "waitting for download file: %s\n", object); 75 76 return status; 77 }
其中 oss_add_cache 函数实现将oss文件下载且缓存到本地:
1 oss_cache_context_t* oss_add_cache(const char* object) 2 { 3 // 缓存到本地内存的hash表中. 同时执行下载操作. 4 5 oss_cache_context_t *context; 6 switch_thread_data_t *td; 7 const char *ext; 8 char uuid[SWITCH_UUID_FORMATTED_LENGTH + 2]; 9 10 static unsigned int seq = 0; 11 12 context = malloc(sizeof(oss_cache_context_t)); 13 if (NULL == context) return NULL; 14 memset(context, 0, sizeof(oss_cache_context_t)); 15 16 context->status = OSS_FILE_DOWNLOADING; 17 context->oss_object = strdup(object); 18 // 需要将object转换到本地文件, 使用uuid. 19 ext = strrchr(object, '.'); 20 if (NULL == ext) ext = ".wav"; 21 switch_uuid_str(uuid, sizeof(uuid)); 22 context->local_path = switch_mprintf("%s/%02d/%s%s", globals.cache_location, (int)(seq++ % OSS_DIR_COUNT), uuid, ext); 23 24 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "add cache, oss object: %s\n", object); 25 26 switch_core_hash_insert(globals.map_cache, context->oss_object, context); 27 28 // 准备下载任务. 29 td = malloc(sizeof(switch_thread_data_t)); 30 if (td) { 31 memset(td, 0, sizeof(switch_thread_data_t)); 32 td->func = oss_download_thread; 33 td->obj = context; 34 td->alloc = 1; 35 switch_thread_pool_launch_thread(&td); 36 } 37 38 return context; 39 }
实际的下载操作在线程中处理, 下载线程处理函数 oss_download_thread:
1 void* SWITCH_THREAD_FUNC oss_download_thread(switch_thread_t* thread, void* param) 2 { 3 oss_cache_context_t* cc = (oss_cache_context_t*)param; 4 5 aos_pool_t *p = NULL; 6 aos_string_t bucket; 7 aos_string_t object; 8 oss_request_options_t *options = NULL; 9 aos_table_t *headers = NULL; 10 aos_table_t *params = NULL; 11 aos_table_t *resp_headers = NULL; 12 aos_status_t *s = NULL; 13 aos_string_t file; 14 15 aos_pool_create(&p, NULL); 16 options = oss_request_options_create(p); 17 18 options->config = oss_config_create(options->pool); 19 20 aos_str_set(&options->config->endpoint, globals.oss_endpoint); 21 aos_str_set(&options->config->access_key_id, globals.oss_access_key_id); 22 aos_str_set(&options->config->access_key_secret, globals.oss_secret_access_key); 23 options->config->is_cname = 0; 24 options->ctl = aos_http_controller_create(options->pool, 0); 25 aos_str_set(&bucket, globals.oss_bucket); 26 aos_str_set(&object, cc->oss_object); 27 headers = aos_table_make(p, 0); 28 aos_str_set(&file, cc->local_path); 29 params = aos_table_make(p, 0); 30 31 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "downloading %s, cache file: %s!\n", cc->oss_object, cc->local_path); 32 33 s = oss_get_object_to_file(options, &bucket, &object, headers, params, &file, &resp_headers); 34 if (aos_status_is_ok(s)) { 35 cc->status = OSS_FILE_READY; 36 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%s downloaded!\n", cc->oss_object); 37 } 38 else 39 { 40 cc->status = OSS_FILE_INVALID; 41 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, 42 "failed to download, code:%d, error_code:%s, error_msg:%s, request_id:%s\n", s->code, 43 s->error_code, s->error_msg, s->req_id); 44 //switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "failed to download %s!\n", cc->oss_object); 45 } 46 47 aos_pool_destroy(p); 48 49 return NULL; 50 }
读文件内容:
1 switch_status_t oss_file_read(switch_file_handle_t *handle, void *data, size_t *len) 2 { 3 oss_playback_context_t*context = (oss_playback_context_t*)handle->private_info; 4 5 if (NULL == context->cache) 6 { 7 return switch_core_file_read(&context->fh, data, len); 8 } 9 10 if (OSS_FILE_READY == context->cache->status) 11 { 12 switch_status_t status; 13 // 缓存再 context->fh 中, 需要设置 预缓存长度. 14 context->fh.pre_buffer_datalen = handle->pre_buffer_datalen; 15 status = switch_core_file_open(&context->fh, 16 context->cache->local_path, 17 handle->channels, 18 handle->samplerate, 19 SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT, 20 NULL); 21 if (SWITCH_STATUS_SUCCESS != status) 22 { 23 return status; 24 } 25 26 context->cache = NULL; 27 handle->samples = context->fh.samples; 28 handle->format = context->fh.format; 29 handle->sections = context->fh.sections; 30 handle->seekable = context->fh.seekable; 31 handle->speed = context->fh.speed; 32 handle->interval = context->fh.interval; 33 handle->channels = context->fh.channels; 34 handle->flags |= SWITCH_FILE_NOMUX; 35 // 本地句柄则不需要预缓存了. 因为根本没有数据... 36 handle->pre_buffer_datalen = 0; 37 38 if (switch_test_flag((&context->fh), SWITCH_FILE_NATIVE)) { 39 switch_set_flag_locked(handle, SWITCH_FILE_NATIVE); 40 } 41 else { 42 switch_clear_flag_locked(handle, SWITCH_FILE_NATIVE); 43 } 44 45 return switch_core_file_read(&context->fh, data, len); 46 } 47 48 if (OSS_FILE_INVALID == context->cache->status) 49 { 50 return SWITCH_STATUS_FALSE; 51 } 52 53 if (*len > 320) 54 { 55 memset(data, 0, 320 * sizeof(short)); 56 *len = 320; 57 } 58 else 59 { 60 memset(data, 0, (*len) * sizeof(short)); 61 } 62 63 handle->sample_count += *len; 64 return SWITCH_STATUS_SUCCESS; 65 }
打开文件写(录音):
1 switch_status_t oss_file_open_write(switch_file_handle_t *handle, const char *object) 2 { 3 oss_record_context_t *context; 4 char uuid[SWITCH_UUID_FORMATTED_LENGTH + 2] = {0}; 5 const char *ext; 6 7 context = switch_core_alloc(handle->memory_pool, sizeof(oss_record_context_t)); 8 9 context->fh.channels = handle->channels; 10 context->fh.native_rate = handle->native_rate; 11 context->fh.samples = handle->samples; 12 context->fh.samplerate = handle->samplerate; 13 context->fh.prefix = handle->prefix; 14 context->fh.pre_buffer_datalen = handle->pre_buffer_datalen; 15 16 switch_uuid_str(uuid, sizeof(uuid)); 17 // 上传到OSS服务器的Key. 18 context->oss_object = switch_core_strdup(handle->memory_pool, object); 19 // 本地缓存文件 20 ext = strrchr(object, '.'); 21 if (NULL == ext) ext = ".wav"; 22 context->local_path = switch_core_sprintf(handle->memory_pool, "%s/%s%s", globals.record_location, uuid, ext); 23 24 if (switch_core_file_open(&context->fh, context->local_path, handle->channels, handle->samplerate, handle->flags, 25 NULL) != SWITCH_STATUS_SUCCESS) { 26 27 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "failed to open temp record file: %s\n", context->local_path); 28 return SWITCH_STATUS_GENERR; 29 } 30 31 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "temp record file: %s\n", context->local_path); 32 handle->private_info = context; 33 handle->pre_buffer_datalen = 0; 34 35 return SWITCH_STATUS_SUCCESS; 36 }
录音完成后的上传操作在线程中, 如果上传失败需要存储在sqlite数据库中, 等待片刻重新上传.
1 void *SWITCH_THREAD_FUNC oss_upload_thread(switch_thread_t *thread, void *param) 2 { 3 oss_upload_context_t *uc = (oss_upload_context_t *)param; 4 5 aos_pool_t *p = NULL; 6 aos_string_t bucket; 7 aos_string_t object; 8 aos_table_t *headers = NULL; 9 aos_table_t *resp_headers = NULL; 10 oss_request_options_t *options = NULL; 11 aos_status_t *s = NULL; 12 aos_string_t file; 13 14 aos_pool_create(&p, NULL); 15 options = oss_request_options_create(p); 16 17 options->config = oss_config_create(options->pool); 18 19 aos_str_set(&options->config->endpoint, globals.oss_endpoint); 20 aos_str_set(&options->config->access_key_id, globals.oss_access_key_id); 21 aos_str_set(&options->config->access_key_secret, globals.oss_secret_access_key); 22 options->config->is_cname = 0; 23 options->ctl = aos_http_controller_create(options->pool, 0); 24 25 headers = aos_table_make(options->pool, 1); 26 apr_table_set(headers, OSS_CONTENT_TYPE, "audio/wav"); 27 aos_str_set(&bucket, globals.oss_bucket); 28 aos_str_set(&object, uc->oss_object); 29 aos_str_set(&file, uc->local_path); 30 31 s = oss_put_object_from_file(options, &bucket, &object, &file, headers, &resp_headers); 32 if (aos_status_is_ok(s)) { 33 // 上传成功. 34 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "upload success, object: %s\n", uc->oss_object); 35 // 删除本地文件. 36 switch_file_remove(uc->local_path, uc->pool); 37 38 if (uc->sqlite) { 39 // 需要从sqlite中删除记录. 40 switch_cache_db_handle_t *dbh; 41 switch_mutex_lock(globals.sql_mutex); 42 if (SWITCH_STATUS_SUCCESS == switch_cache_db_get_db_handle_dsn(&dbh, OSS_SQLITE_NAME)) { 43 char *sql = switch_mprintf("delete from files where oss_object = '%q';", uc->oss_object); 44 if (sql) { 45 switch_cache_db_execute_sql(dbh, sql, NULL); 46 switch_safe_free(sql); 47 } 48 switch_cache_db_release_db_handle(&dbh); 49 } 50 switch_mutex_unlock(globals.sql_mutex); 51 } 52 } else { 53 // 上传失败, 文件信息需要缓存到sqlite中. 54 55 switch_cache_db_handle_t *dbh; 56 57 // 缓存? 需要重新上传. 58 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, 59 "failed to upload, code:%d, error_code:%s, error_msg:%s, record file:%s, object:%s\n", 60 s->code, s->error_code, s->error_msg, uc->local_path, uc->oss_object); 61 // switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "failed to upload\n"); 62 63 // 记录文件信息. 等候重新上传. 64 // 为什么使用sqlite, 而不是内存queue? 65 // 防止重启程序丢失未上传的录音数据. 66 if (!uc->sqlite) { 67 switch_mutex_lock(globals.sql_mutex); 68 if (SWITCH_STATUS_SUCCESS == switch_cache_db_get_db_handle_dsn(&dbh, OSS_SQLITE_NAME)) { 69 char *sql = switch_mprintf("insert into files (oss_object, path) values('%q','%q');", uc->oss_object, 70 uc->local_path); 71 if (sql) { 72 switch_cache_db_execute_sql(dbh, sql, NULL); 73 switch_safe_free(sql); 74 75 // 需要一个线程, 检查本SQLite中没有上传的数据. 76 globals.upload_sqlite = SWITCH_TRUE; 77 } 78 switch_cache_db_release_db_handle(&dbh); 79 } 80 switch_mutex_unlock(globals.sql_mutex); 81 } else { 82 // 失败了, 还要重新上传. 83 switch_mutex_lock(globals.sql_mutex); 84 globals.upload_sqlite = SWITCH_TRUE; 85 switch_mutex_unlock(globals.sql_mutex); 86 } 87 } 88 89 aos_pool_destroy(p); 90 91 return NULL; 92 }
如上基本上oss文件的放音和录音功能基本实现, 实际代码还有其它处理, 比如, 为了降低打开oss文件的延时, 打开操作实际交给线程池处理等.
暂时写这么多,以后再补充.

浙公网安备 33010602011771号