From fe3e18c4a3e4a7f99479eab4922684a5d4e7bc43 Mon Sep 17 00:00:00 2001 From: EvalZero Date: Wed, 17 Jul 2019 16:48:16 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E3=80=90=E5=A2=9E=E5=8A=A0=E3=80=91?= =?UTF-8?q?=E5=BB=BA=E7=AB=8B=E5=B7=A5=E7=A8=8B=EF=BC=8C=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E6=BA=90=E4=BB=A3=E7=A0=81=E5=92=8C=E4=BD=BF=E7=94=A8=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 115 +++++++++- SConscript | 25 +++ inc/wavhdr.h | 81 +++++++ inc/wavplayer.h | 94 +++++++++ inc/wavrecorder.h | 50 +++++ src/wavhdr.c | 153 ++++++++++++++ src/wavplayer.c | 478 ++++++++++++++++++++++++++++++++++++++++++ src/wavplayer_cmd.c | 194 +++++++++++++++++ src/wavrecorder.c | 248 ++++++++++++++++++++++ src/wavrecorder_cmd.c | 132 ++++++++++++ 10 files changed, 1569 insertions(+), 1 deletion(-) create mode 100644 SConscript create mode 100644 inc/wavhdr.h create mode 100644 inc/wavplayer.h create mode 100644 inc/wavrecorder.h create mode 100644 src/wavhdr.c create mode 100644 src/wavplayer.c create mode 100644 src/wavplayer_cmd.c create mode 100644 src/wavrecorder.c create mode 100644 src/wavrecorder_cmd.c diff --git a/README.md b/README.md index ade5b8b..27573c8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,115 @@ # wavplayer -Minimal music player for wav file. + +## 1. 简介 + +**wavplayer** 是一个简易的 wav 格式的音乐播放器,提供播放和录制 wav 文件的功能,支持播放、停止、暂停、恢复,以及音量调节等功能。 + +### 1.1. 文件结构 + +| 文件夹 | 说明 | +| ---- | ---- | +| src | 核心源码,主要实现 wav 播放和录音,以及导出 Finsh 命令行 | +| inc | 头文件目录 | + +### 1.2 许可证 + +wavplayer package 遵循 Apache 2.0 许可,详见 `LICENSE` 文件。 + +### 1.3 依赖 + +- RT-Thread 4.0+ +- RT-Thread Audio 驱动框架 +- optparse 命令行参数解析软件包 + +### 1.4 配置宏说明 + +```shell + --- WavPlayer: Minimal music player for wav file play and record. + [*] Enable support for play + (sound0) The play device name + [*] Enable support for record + (sound0) The record device name + Version (v1.0.0) ---> +``` + +**Enable support for play**:使能wav播放功能 +**The play device name**:指定播放使用的声卡设备,默认`sound0` +**Enable support for record**:使能wav录音功能 +**The record device name**:指定录音使用的声卡设备,默认和播放一致,使用`sound0` + +## 2. 使用 + +wavplayer 的常用功能已经导出到 Finsh 命令行,下面是使用示例。 + +### 2.1 播放功能 + +- 开始播放 + +```shell +msh /> +msh />wavplay -s song_44.wav +Information: +sampletate 44100 +channels 2 +sample bits width 16 +[I/WAV_PLAYER] play start, uri=song_44.wav +``` + +- 停止播放 + +```shell +msh />wavplay -t +[I/WAV_PLAYER] play end +``` + +- 暂停播放 + +```shell +msh /> +msh />wavplay -p +msh /> +``` + +- 恢复播放 + +```shell +msh /> +msh />wavplay -p +msh /> +``` + +- 设置音量 + +```shell +msh /> +msh />wavplay -v 88 +msh /> +``` + +### 2.2 录音功能 + +- 开始录音 + +```shell +msh />wavrecord -s test.wav +Information: +sampletate 8000 +channels 2 +``` + +- 停止录音 + +```shell +msh /> +msh />wavrecord -t +msh /> +``` + +## 3. 注意事项 + +- 仅支持采样位数为 16bit 的音频 + +## 4. 联系方式 + +- 维护:Zero-Free +- 主页:https://github.com/RT-Thread-packages/wavplayer diff --git a/SConscript b/SConscript new file mode 100644 index 0000000..fbe23bc --- /dev/null +++ b/SConscript @@ -0,0 +1,25 @@ +Import('rtconfig') +from building import * + +cwd = GetCurrentDir() + +path = [cwd, + cwd + '/inc'] + +src = Split('src/wavhdr.c') + +if GetDepend(['PKG_WP_USING_PLAY']): + src += Split(''' + src/wavplayer.c + src/wavplayer_cmd.c + ''') + +if GetDepend(['PKG_WP_USING_RECORD']): + src += Split(''' + src/wavrecorder.c + src/wavrecorder_cmd.c + ''') + +group = DefineGroup('wavplayer', src, depend = ['PKG_USING_WAVPLAYER'], CPPPATH = path) + +Return('group') diff --git a/inc/wavhdr.h b/inc/wavhdr.h new file mode 100644 index 0000000..29595ff --- /dev/null +++ b/inc/wavhdr.h @@ -0,0 +1,81 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Date Author Notes + * 2019-07-15 Zero-Free first implementation + */ + +#ifndef __WAVHDR_H__ +#define __WAVHDR_H__ + +#include + +struct wav_header +{ + char riff_id[4]; /* "RIFF" */ + int riff_datasize; /* RIFF chunk data size,exclude riff_id[4] and riff_datasize,total - 8 */ + + char riff_type[4]; /* "WAVE" */ + + char fmt_id[4]; /* "fmt " */ + int fmt_datasize; /* fmt chunk data size,16 for pcm */ + short fmt_compression_code; /* 1 for PCM */ + short fmt_channels; /* 1(mono) or 2(stereo) */ + int fmt_sample_rate; /* samples per second */ + int fmt_avg_bytes_per_sec; /* sample_rate * channels * bit_per_sample / 8 */ + short fmt_block_align; /* number bytes per sample, bit_per_sample * channels / 8 */ + short fmt_bit_per_sample; /* bits of each sample(8,16,32). */ + + char data_id[4]; /* "data" */ + int data_datasize; /* data chunk size,pcm_size - 44 */ +}; + +/** + * @brief Initialize wavfile header + * + * @param header the pointer for wavfile header + * @param channels wavfile channels + * @param sample_rate wavfile samplerate + * @param datasize wavfile total data size + * + * @return + * - 0 Success + * - -1 Error + */ +int wavheader_init(struct wav_header *header, int sample_rate, int channels, int datasize); + +/** + * @brief Read wavfile head information from file stream + * + * @param header the pointer for wavfile header + * @param fp file stream + * + * @return + * - 0 Success + * - -1 Error + */ +int wavheader_read(struct wav_header *header, FILE *fp); + +/** + * @brief Write wavfile head information to file stream + * + * @param header the pointer for wavfile header + * @param fp file stream + * + * @return + * - 0 Success + * - -1 Error + */ +int wavheader_write(struct wav_header *header, FILE *fp); + +/** + * @brief Print wavfile header information + * + * @param header the pointer for wavfile header + * + * @return + * - None + */ +void wavheader_print(struct wav_header *header); + +#endif diff --git a/inc/wavplayer.h b/inc/wavplayer.h new file mode 100644 index 0000000..39ca7e4 --- /dev/null +++ b/inc/wavplayer.h @@ -0,0 +1,94 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Date Author Notes + * 2019-07-15 Zero-Free first implementation + */ + +#ifndef __WAVPLAYER_H__ +#define __WAVPLAYER_H__ + +/** + * wav player status + */ +enum PLAYER_STATE +{ + PLAYER_STATE_STOPED = 0, + PLAYER_STATE_PLAYING = 1, + PLAYER_STATE_PAUSED = 2, +}; + +/** + * @brief Play wav music + * + * @param uri the pointer for file path + * + * @return + * - 0 Success + * - others Failed + */ +int wavplayer_play(char *uri); + +/** + * @brief Stop music + * + * @return + * - 0 Success + * - others Failed + */ +int wavplayer_stop(void); + +/** + * @brief Pause music + * + * @return + * - 0 Success + * - others Failed + */ +int wavplayer_pause(void); + +/** + * @brief Resume music + * + * @return + * - 0 Success + * - others Failed + */ +int wavplayer_resume(void); + +/** + * @brief Sev volume + * + * @param volume volume value(0 ~ 99) + * + * @return + * - 0 Success + * - others Failed + */ +int wavplayer_volume_set(int volume); + +/** + * @brief Get volume + * + * @return volume value(0~00) + */ +int wavplayer_volume_get(void); + +/** + * @brief Get wav player state + * + * @return + * - PLAYER_STATE_STOPED stoped status + * - PLAYER_STATE_PLAYING playing status + * - PLAYER_STATE_PAUSED paused + */ +int wavplayer_state_get(void); + +/** + * @brief Get the uri that is currently playing + * + * @return uri that is currently playing + */ +char *wavplayer_uri_get(void); + +#endif diff --git a/inc/wavrecorder.h b/inc/wavrecorder.h new file mode 100644 index 0000000..fbc5ea1 --- /dev/null +++ b/inc/wavrecorder.h @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Date Author Notes + * 2019-07-16 Zero-Free first implementation + */ + +#ifndef __WAVRECORDER_H__ +#define __WAVRECORDER_H__ + +#include + +struct wavrecord_info +{ + char *uri; + rt_uint32_t samplerate; + rt_uint16_t channels; + rt_uint16_t samplefmt; +}; + +/** + * @brief Start to record + * + * @param info wavfile informations + * + * @return + * - RT_EOK Success + * - < 0 Failed + */ +rt_err_t wavrecorder_start(struct wavrecord_info *info); + +/** + * @brief Stop record + * + * @return + * - RT_EOK Success + * - < 0 Failed + */ +rt_err_t wavrecorder_stop(void); + +/** + * @brief Get recorder status + * + * @return + * - RT_TRUE actived + * - RT_FALSE non-actived + */ +rt_bool_t wavrecorder_is_actived(void); + +#endif diff --git a/src/wavhdr.c b/src/wavhdr.c new file mode 100644 index 0000000..b9ebf14 --- /dev/null +++ b/src/wavhdr.c @@ -0,0 +1,153 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Date Author Notes + * 2019-06-28 Zero-Free first implementation + */ + +#include +#include + +/* read and write integer from file stream */ +static int get_int(FILE *fp) +{ + char *s; + int i; + s = (char *)&i; + size_t len = sizeof(int); + int n = 0; + for (; n < len; n++) + { + s[n] = getc(fp); + } + return i; +} + +static int put_int(int i, FILE *fp) +{ + char *s; + s = (char *)&i; + size_t len = sizeof(int); + int n = 0; + for (; n < len; n++) + { + putc(s[n], fp); + } + + return i; +} + +static short int get_sint(FILE *fp) +{ + char *s; + short int i; + s = (char *)&i; + size_t len = sizeof(short); + int n = 0; + for (; n < len; n++) + { + s[n] = getc(fp); + } + + return i; +} + +static short int put_sint(short int i, FILE *fp) +{ + char *s; + s = (char *)&i; + size_t len = sizeof(short); + int n = 0; + for (; n < len; n++) + { + putc(s[n], fp); + }; + + return i; +} + +int wavheader_init(struct wav_header *header, int sample_rate, int channels, int datasize) +{ + if (header == NULL) + return -1; + + memcpy(header->riff_id, "RIFF", 4); + header->riff_datasize = datasize + 44 - 8; + + memcpy(header->riff_type, "WAVE", 4); + + memcpy(header->fmt_id, "fmt ", 4); + header->fmt_datasize = 16; + header->fmt_compression_code = 1; + header->fmt_channels = channels; + header->fmt_sample_rate = sample_rate; + header->fmt_avg_bytes_per_sec = header->fmt_sample_rate * header->fmt_channels * header->fmt_bit_per_sample / 8; + header->fmt_block_align = header->fmt_bit_per_sample * header->fmt_channels / 8; + header->fmt_bit_per_sample = 16; + + memcpy(header->data_id, "data", 4); + header->data_datasize = datasize; + + return 0; +} + +int wavheader_read(struct wav_header *header, FILE *fp) +{ + if (fp == NULL) + return -1; + + fread(header->riff_id, 4, 1, fp); + header->riff_datasize = get_int(fp); + fread(header->riff_type, 4, 1, fp); + fread(header->fmt_id, 4, 1, fp); + header->fmt_datasize = get_int(fp); + header->fmt_compression_code = get_sint(fp); + header->fmt_channels = get_sint(fp); + header->fmt_sample_rate = get_int(fp); + header->fmt_avg_bytes_per_sec = get_int(fp); + header->fmt_block_align = get_sint(fp); + header->fmt_bit_per_sample = get_sint(fp); + fread(header->data_id, 4, 1, fp); + header->data_datasize = get_int(fp); + + return 0; +} + +int wavheader_write(struct wav_header *header, FILE *fp) +{ + if (fp == NULL) + return -1; + + fwrite(header->riff_id, 4, 1, fp); + put_int(header->riff_datasize, fp); + fwrite(header->riff_type, 4, 1, fp); + fwrite(header->fmt_id, 4, 1, fp); + put_int(header->fmt_datasize, fp); + put_sint(header->fmt_compression_code, fp); + put_sint(header->fmt_channels, fp); + put_int(header->fmt_sample_rate, fp); + put_int(header->fmt_avg_bytes_per_sec, fp); + put_sint(header->fmt_block_align, fp); + put_sint(header->fmt_bit_per_sample, fp); + fwrite(header->data_id, 4, 1, fp); + put_int(header->data_datasize, fp); + + return 0; +} + +void wavheader_print(struct wav_header *header) +{ + printf("header.riff_id: %c%c%c%c\n", header->riff_id[0], header->riff_id[1], header->riff_id[2], header->riff_id[3]); + printf("header.riff_datasize: %d\n", header->riff_datasize); + printf("header.riff_type: %c%c%c%c\n", header->riff_type[0], header->riff_type[1], header->riff_type[2], header->riff_type[3]); + printf("header.fmt_id: %c%c%c%c\n", header->fmt_id[0], header->fmt_id[1], header->fmt_id[2], header->fmt_id[3]); + printf("header.fmt_datasize: %d\n", header->fmt_datasize); + printf("header.fmt_compression_code: %hd\n", header->fmt_compression_code); + printf("header.fmt_channels: %hd\n", header->fmt_channels); + printf("header.fmt_sample_rate: %d\n", header->fmt_sample_rate); + printf("header.fmt_avg_bytes_per_sec: %d\n", header->fmt_avg_bytes_per_sec); + printf("header.fmt_block_align: %hd\n", header->fmt_block_align); + printf("header.fmt_bit_per_sample: %hd\n", header->fmt_bit_per_sample); + printf("header.data_id: %c%c%c%c\n", header->data_id[0], header->data_id[1], header->data_id[2], header->data_id[3]); + printf("header.data_datasize: %d\n", header->data_datasize); +} diff --git a/src/wavplayer.c b/src/wavplayer.c new file mode 100644 index 0000000..b57e556 --- /dev/null +++ b/src/wavplayer.c @@ -0,0 +1,478 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Date Author Notes + * 2019-07-15 Zero-Free first implementation + */ + +#include +#include +#include +#include + +#define DBG_TAG "WAV_PLAYER" +#define DBG_LVL DBG_INFO +#include + +#define VOLUME_MIN (0) +#define VOLUME_MAX (99) + +#define WP_BUFFER_SIZE (2048) +#define WP_VOLUME_DEFAULT (55) +#define WP_MSG_SIZE (10) +#define WP_THREAD_STATCK_SIZE (2048) +#define WP_THREAD_PRIORITY (21) + +enum MSG_TYPE +{ + MSG_NONE = 0, + MSG_START = 1, + MSG_STOP = 2, + MSG_PAUSE = 3, + MSG_RESUME = 4, +}; + +enum PLAYER_EVENT +{ + PLAYER_EVENT_NONE = 0, + PLAYER_EVENT_PLAY = 1, + PLAYER_EVENT_STOP = 2, + PLAYER_EVENT_PAUSE = 3, + PLAYER_EVENT_RESUME = 4, +}; + +struct play_msg +{ + int type; + void *data; +}; + +struct wavplayer +{ + int state; + char *uri; + char *buffer; + rt_device_t device; + rt_mq_t mq; + rt_mutex_t lock; + struct rt_completion ack; + FILE *fp; + int volume; +}; + +static struct wavplayer player; + +#if (DBG_LEVEL >= DBG_LOG) + +static const char *state_str[] = +{ + "STOPPED", + "PLAYING", + "PAUSED", +}; + +static const char *event_str[] = +{ + "NONE", + "PLAY" + "STOP" + "PAUSE" + "RESUME" +}; + +#endif + +static void play_lock(void) +{ + rt_mutex_take(player.lock, RT_WAITING_FOREVER); +} + +static void play_unlock(void) +{ + rt_mutex_release(player.lock); +} + +static rt_err_t play_msg_send(struct wavplayer *player, int type, void *data) +{ + struct play_msg msg; + + msg.type = type; + msg.data = data; + + return rt_mq_send(player->mq, &msg, sizeof(struct play_msg)); +} + +int wavplayer_play(char *uri) +{ + rt_err_t result = RT_EOK; + + rt_completion_init(&player.ack); + + play_lock(); + if (player.state != PLAYER_STATE_STOPED) + { + wavplayer_stop(); + } + if (player.uri) + { + rt_free(player.uri); + } + player.uri = rt_strdup(uri); + result = play_msg_send(&player, MSG_START, RT_NULL); + rt_completion_wait(&player.ack, RT_WAITING_FOREVER); + play_unlock(); + + return result; +} + +int wavplayer_stop(void) +{ + rt_err_t result = RT_EOK; + + rt_completion_init(&player.ack); + + play_lock(); + if (player.state != PLAYER_STATE_STOPED) + { + result = play_msg_send(&player, MSG_STOP, RT_NULL); + rt_completion_wait(&player.ack, RT_WAITING_FOREVER); + } + play_unlock(); + + return result; +} + +int wavplayer_pause(void) +{ + rt_err_t result = RT_EOK; + + rt_completion_init(&player.ack); + + play_lock(); + if (player.state == PLAYER_STATE_PLAYING) + { + result = play_msg_send(&player, MSG_PAUSE, RT_NULL); + rt_completion_wait(&player.ack, RT_WAITING_FOREVER); + } + play_unlock(); + + return result; +} + +int wavplayer_resume(void) +{ + rt_err_t result = RT_EOK; + + rt_completion_init(&player.ack); + + play_lock(); + if (player.state == PLAYER_STATE_PAUSED) + { + result = play_msg_send(&player, MSG_RESUME, RT_NULL); + rt_completion_wait(&player.ack, RT_WAITING_FOREVER); + } + play_unlock(); + + return result; +} + +int wavplayer_volume_set(int volume) +{ + struct rt_audio_caps caps; + + if (volume < VOLUME_MIN) + volume = VOLUME_MIN; + else if (volume > VOLUME_MAX) + volume = VOLUME_MAX; + + player.device = rt_device_find(PKG_WP_PLAY_DEVICE); + if (player.device == RT_NULL) + return -RT_ERROR; + + player.volume = volume; + caps.main_type = AUDIO_TYPE_MIXER; + caps.sub_type = AUDIO_MIXER_VOLUME; + caps.udata.value = volume; + + LOG_D("set volume = %d", volume); + return rt_device_control(player.device, AUDIO_CTL_CONFIGURE, &caps); +} + +int wavplayer_volume_get(void) +{ + return player.volume; +} + +int wavplayer_state_get(void) +{ + return player.state; +} + +char *wavplayer_uri_get(void) +{ + return player.uri; +} + +static rt_err_t wavplayer_open(struct wavplayer *player) +{ + rt_err_t result = RT_EOK; + struct rt_audio_caps caps; + struct wav_header wav; + + /* find device */ + player->device = rt_device_find(PKG_WP_PLAY_DEVICE); + if (player->device == RT_NULL) + { + LOG_E("device %s not find", PKG_WP_PLAY_DEVICE); + return - RT_ERROR; + } + + /* open file */ + player->fp = fopen(player->uri, "rb"); + if (player->fp == RT_NULL) + { + LOG_E("open file %s failed", player->uri); + result = -RT_ERROR; + goto __exit; + } + + /* open sound device */ + result = rt_device_open(player->device, RT_DEVICE_OFLAG_WRONLY); + if (result != RT_EOK) + { + LOG_E("open %s device faield", PKG_WP_PLAY_DEVICE); + goto __exit; + } + + LOG_D("open wavplayer, device %s", PKG_WP_PLAY_DEVICE); + /* read wavfile header information from file */ + wavheader_read(&wav, player->fp); + + rt_kprintf("Information:\n"); + rt_kprintf("sampletate %d\n", wav.fmt_sample_rate); + rt_kprintf("channels %d\n", wav.fmt_channels); + rt_kprintf("sample bits width %d\n", wav.fmt_bit_per_sample); + + /* set sampletate,channels, samplefmt */ + caps.main_type = AUDIO_TYPE_OUTPUT; + caps.sub_type = AUDIO_DSP_PARAM; + caps.udata.config.samplerate = wav.fmt_sample_rate; + caps.udata.config.channels = wav.fmt_channels; + caps.udata.config.samplefmt = AUDIO_FMT_PCM_S16_LE; + rt_device_control(player->device, AUDIO_CTL_CONFIGURE, &caps); + + /* set volume according to configuration */ + caps.main_type = AUDIO_TYPE_MIXER; + caps.sub_type = AUDIO_MIXER_VOLUME; + caps.udata.value = player->volume; + rt_device_control(player->device, AUDIO_CTL_CONFIGURE, &caps); + + return RT_EOK; + +__exit: + if (player->fp) + { + fclose(player->fp); + player->fp = RT_NULL; + } + + if (player->device) + { + rt_device_close(player->device); + player->device = RT_NULL; + } + + return result; +} + + +static void wavplayer_close(struct wavplayer *player) +{ + if (player->fp) + { + fclose(player->fp); + player->fp = RT_NULL; + } + + if (player->device) + { + rt_device_close(player->device); + player->device = RT_NULL; + } + + LOG_D("close wavplayer"); +} + +static int wavplayer_event_handler(struct wavplayer *player, int timeout) +{ + int event; + rt_err_t result; + struct play_msg msg; +#if (DBG_LEVEL >= DBG_LOG) + rt_uint8_t last_state; +#endif + + result = rt_mq_recv(player->mq, &msg, sizeof(struct play_msg), timeout); + if (result != RT_EOK) + { + event = PLAYER_EVENT_NONE; + return event; + } + +#if (DBG_LEVEL >= DBG_LOG) + last_state = player->state; +#endif + + switch (msg.type) + { + case MSG_START: + event = PLAYER_EVENT_PLAY; + player->state = PLAYER_STATE_PLAYING; + break; + + case MSG_STOP: + event = PLAYER_EVENT_STOP; + player->state = PLAYER_STATE_STOPED; + break; + + case MSG_PAUSE: + event = PLAYER_EVENT_PAUSE; + player->state = PLAYER_STATE_PAUSED; + break; + + case MSG_RESUME: + event = PLAYER_EVENT_RESUME; + player->state = PLAYER_STATE_PLAYING; + break; + + default: + event = PLAYER_EVENT_NONE; + break; + } + + rt_completion_done(&player->ack); + +#if (DBG_LEVEL >= DBG_LOG) + LOG_D("EVENT:%s, STATE:%s -> %s", event_str[event], state_str[last_state], state_str[player->state]); +#endif + + return event; +} + +static void wavplayer_entry(void *parameter) +{ + rt_err_t result = RT_EOK; + rt_int32_t size; + int event; + + player.buffer = rt_malloc(WP_BUFFER_SIZE); + if (player.buffer == RT_NULL) + return; + rt_memset(player.buffer, 0, WP_BUFFER_SIZE); + + player.mq = rt_mq_create("wav_p", 10, sizeof(struct play_msg), RT_IPC_FLAG_FIFO); + if (player.mq == RT_NULL) + goto __exit; + + player.lock = rt_mutex_create("wav_p", RT_IPC_FLAG_FIFO); + if (player.lock == RT_NULL) + goto __exit; + + player.volume = WP_VOLUME_DEFAULT; + + while (1) + { + /* wait play event forever */ + event = wavplayer_event_handler(&player, RT_WAITING_FOREVER); + if (event != PLAYER_EVENT_PLAY) + continue; + + /* open wavplayer */ + result = wavplayer_open(&player); + if (result != RT_EOK) + { + player.state = PLAYER_STATE_STOPED; + LOG_I("open wav player failed"); + continue; + } + + LOG_I("play start, uri=%s", player.uri); + while (1) + { + event = wavplayer_event_handler(&player, RT_WAITING_NO); + + switch (event) + { + case PLAYER_EVENT_NONE: + { + /* read raw data from file stream */ + size = fread(player.buffer, WP_BUFFER_SIZE, 1, player.fp); + if (size != 1) + { + /* FILE END*/ + player.state = PLAYER_STATE_STOPED; + } + else + { + /*witte data to sound device*/ + rt_device_write(player.device, 0, player.buffer, WP_BUFFER_SIZE); + } + break; + } + + case PLAYER_EVENT_PAUSE: + { + /* wait resume or stop event forever */ + event = wavplayer_event_handler(&player, RT_WAITING_FOREVER); + } + + default: + break; + } + + if (player.state == PLAYER_STATE_STOPED) + break; + } + + /* close wavplayer */ + wavplayer_close(&player); + LOG_I("play end"); + } + +__exit: + if (player.buffer) + { + rt_free(player.buffer); + player.buffer = RT_NULL; + } + + if (player.mq) + { + rt_mq_delete(player.mq); + player.mq = RT_NULL; + } + + if (player.lock) + { + rt_mutex_delete(player.lock); + player.lock = RT_NULL; + } +} + + +int wavplayer_init(void) +{ + rt_thread_t tid; + + tid = rt_thread_create("wav_p", + wavplayer_entry, + RT_NULL, + WP_THREAD_STATCK_SIZE, + WP_THREAD_PRIORITY, 10); + if (tid) + rt_thread_startup(tid); + + return RT_EOK; +} + +INIT_APP_EXPORT(wavplayer_init); diff --git a/src/wavplayer_cmd.c b/src/wavplayer_cmd.c new file mode 100644 index 0000000..539d6aa --- /dev/null +++ b/src/wavplayer_cmd.c @@ -0,0 +1,194 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Date Author Notes + * 2019-07-15 Zero-Free first implementation + */ + +#include +#include +#include +#include + +#include + +enum WAVPLAYER_ACTTION +{ + WAVPLAYER_ACTION_HELP = 0, + WAVPLAYER_ACTION_START = 1, + WAVPLAYER_ACTION_STOP = 2, + WAVPLAYER_ACTION_PAUSE = 3, + WAVPLAYER_ACTION_RESUME = 4, + WAVPLAYER_ACTION_VOLUME = 5, + WAVPLAYER_ACTION_DUMP = 6, +}; + +struct wavplay_args +{ + int action; + char *uri; + int volume; +}; + +static const char *state_str[] = +{ + "STOPPED", + "PLAYING", + "PAUSED", +}; + +static struct optparse_long opts[] = +{ + {"help", 'h', OPTPARSE_NONE }, /* 帮助 */ + {"start", 's', OPTPARSE_REQUIRED}, /* 播放 */ + {"stop", 't', OPTPARSE_NONE }, /* 停止 */ + {"pause", 'p', OPTPARSE_NONE }, /* 暂停 */ + {"resume", 'r', OPTPARSE_NONE }, /* 恢复 */ + {"volume", 'v', OPTPARSE_REQUIRED}, /* 音量 */ + {"dump", 'd', OPTPARSE_NONE }, /* 状态 */ + { NULL, 0, OPTPARSE_NONE } +}; + +static void usage(void) +{ + rt_kprintf("usage: wavplay [option] [target] ...\n\n"); + rt_kprintf("usage options:\n"); + rt_kprintf(" -h, --help Print defined help message.\n"); + rt_kprintf(" -s URI, --start=URI Play wav music with URI(local files).\n"); + rt_kprintf(" -t, --stop Stop playing music.\n"); + rt_kprintf(" -p, --pause Pause the music.\n"); + rt_kprintf(" -r, --resume Resume the music.\n"); + rt_kprintf(" -v lvl, --volume=lvl Change the volume(0~99).\n"); + rt_kprintf(" -d, --dump Dump play relevant information.\n"); +} + +static void dump_status(void) +{ + rt_kprintf("\nwavplayer status:\n"); + rt_kprintf("uri - %s\n", wavplayer_uri_get()); + rt_kprintf("status - %s\n", state_str[wavplayer_state_get()]); + rt_kprintf("volume - %d\n", wavplayer_volume_get()); +} + +int wavplay_args_prase(int argc, char *argv[], struct wavplay_args *play_args) +{ + int ch; + int option_index; + struct optparse options; + rt_uint8_t action_cnt = 0; + rt_err_t result = RT_EOK; + + if (argc == 1) + { + play_args->action = WAVPLAYER_ACTION_HELP; + return RT_EOK; + } + + /* Parse cmd */ + optparse_init(&options, argv); + while ((ch = optparse_long(&options, opts, &option_index)) != -1) + { + switch (ch) + { + case 'h': /* 帮助 */ + play_args->action = WAVPLAYER_ACTION_HELP; + break; + + case 's': /* 播放 */ + play_args->action = WAVPLAYER_ACTION_START; + play_args->uri = options.optarg; + action_cnt++; + break; + + case 't': /* 停止 */ + play_args->action = WAVPLAYER_ACTION_STOP; + action_cnt++; + break; + + case 'p': /* 暂停 */ + play_args->action = WAVPLAYER_ACTION_PAUSE; + action_cnt++; + break; + + case 'r': /* 恢复 */ + play_args->action = WAVPLAYER_ACTION_RESUME; + action_cnt++; + break; + + case 'v': /* 音量 */ + play_args->action = WAVPLAYER_ACTION_VOLUME; + play_args->volume = (options.optarg == RT_NULL) ? (-1) : atoi(options.optarg); + action_cnt++; + break; + + case 'd': /* 信息 */ + play_args->action = WAVPLAYER_ACTION_DUMP; + break; + + default: + result = -RT_EINVAL; + break; + } + } + + /* 判断 播放 暂停 停止 恢复 移动 命令是否多次使用 不能共存使用 */ + if (action_cnt > 1) + { + rt_kprintf("START STOP PAUSE RESUME parameter can't be used at the same time.\n"); + result = -RT_EINVAL; + } + + return result; +} + +int wav_player(int argc, char *argv[]) +{ + int result = RT_EOK; + struct wavplay_args play_args = {0}; + + result = wavplay_args_prase(argc, argv, &play_args); + if (result != RT_EOK) + { + usage(); + return result; + } + + switch (play_args.action) + { + case WAVPLAYER_ACTION_HELP: + usage(); + break; + + case WAVPLAYER_ACTION_START: + wavplayer_play(play_args.uri); + break; + + case WAVPLAYER_ACTION_STOP: + wavplayer_stop(); + break; + + case WAVPLAYER_ACTION_PAUSE: + wavplayer_pause(); + break; + + case WAVPLAYER_ACTION_RESUME: + wavplayer_resume(); + break; + + case WAVPLAYER_ACTION_VOLUME: + wavplayer_volume_set(play_args.volume); + break; + + case WAVPLAYER_ACTION_DUMP: + dump_status(); + break; + + default: + result = -RT_ERROR; + break; + } + + return result; +} + +MSH_CMD_EXPORT_ALIAS(wav_player, wavplay, play wav music); diff --git a/src/wavrecorder.c b/src/wavrecorder.c new file mode 100644 index 0000000..d6c493a --- /dev/null +++ b/src/wavrecorder.c @@ -0,0 +1,248 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Date Author Notes + * 2019-07-16 Zero-Free first implementation + */ + +#include +#include +#include +#include + +#define DBG_TAG "WAV_RECORDER" +#define DBG_LVL DBG_INFO +#include + +struct recorder +{ + rt_device_t device; + struct wavrecord_info info; + struct rt_event *event; + struct rt_completion ack; + rt_uint8_t *buffer; + FILE *fp; + rt_bool_t activated; +}; + +enum RECORD_EVENT +{ + RECORD_EVENT_STOP = 0x01, + RECORD_EVENT_START = 0x02, +}; + +#define WR_BUFFER_SIZE (2048) + +static struct recorder record; + +static rt_err_t wavrecorder_open(struct recorder *record) +{ + rt_err_t result = RT_EOK; + + record->device = rt_device_find(PKG_WP_RECORD_DEVICE); + if (record->device == RT_NULL) + { + LOG_E("device %s not find", PKG_WP_RECORD_DEVICE); + return -RT_ERROR; + } + + /* malloc internal buffer */ + record->buffer = rt_malloc(WR_BUFFER_SIZE); + if (record->buffer == RT_NULL) + { + result = -RT_ENOMEM; + LOG_E("malloc internal buffer for recorder failed"); + goto __exit; + } + rt_memset(record->buffer, 0, WR_BUFFER_SIZE); + + /* open file */ + record->fp = fopen(record->info.uri, "wb+"); + if (record->fp == RT_NULL) + { + result = -RT_ERROR; + LOG_E("open file %s failed", record->info.uri); + goto __exit; + } + + /* open micphone device */ + result = rt_device_open(record->device, RT_DEVICE_OFLAG_RDONLY); + if (result != RT_EOK) + { + result = -RT_ERROR; + LOG_E("open %s device faield", PKG_WP_RECORD_DEVICE); + goto __exit; + } + + record->event = rt_event_create("wav_r", RT_IPC_FLAG_FIFO); + if (record->event == RT_NULL) + { + result = -RT_ERROR; + LOG_E("create event for wav recorder failed"); + goto __exit; + } + + return RT_EOK; + +__exit: + if (record->buffer) + { + rt_free(record->buffer); + record->buffer = RT_NULL; + } + + if (record->fp) + { + fclose(record->fp); + record->fp = RT_NULL; + } + + if (record->device) + { + rt_device_close(record->device); + record->device = RT_NULL; + } + + if (record->event) + { + rt_event_delete(record->event); + record->event = RT_NULL; + } + + return result; +} + +void wavrecorder_close(struct recorder *record) +{ + if (record->buffer) + { + rt_free(record->buffer); + record->buffer = RT_NULL; + } + + if (record->fp) + { + fclose(record->fp); + record->fp = RT_NULL; + } + + if (record->device) + { + rt_device_close(record->device); + record->device = RT_NULL; + } + + if (record->event) + { + rt_event_delete(record->event); + record->event = RT_NULL; + } +} + +static void wavrecord_entry(void *parameter) +{ + rt_err_t result; + rt_size_t size; + struct wav_header wav = {0}; + struct rt_audio_caps caps; + rt_uint32_t recv_evt, total_length = 0; + + result = wavrecorder_open(&record); + if (result != RT_EOK) + { + LOG_E("open wav recorder failed"); + return; + } + + record.activated = RT_TRUE; + + /* write 44 bytes wavheader */ + fwrite(&wav, 44, 1, record.fp); + + rt_kprintf("Information:\n"); + rt_kprintf("sampletate %d\n", record.info.samplerate); + rt_kprintf("channels %d\n", record.info.channels); + + /* set sampletate,channels, samplefmt */ + caps.main_type = AUDIO_TYPE_INPUT; + caps.sub_type = AUDIO_DSP_PARAM; + caps.udata.config.samplerate = record.info.samplerate; + caps.udata.config.channels = record.info.channels; + caps.udata.config.samplefmt = AUDIO_FMT_PCM_S16_LE; + rt_device_control(record.device, AUDIO_CTL_CONFIGURE, &caps); + + LOG_D("ready to record, device %s, uri %s", PKG_WP_PLAY_DEVICE, record.uri); + + while (1) + { + /* read raw data from sound device */ + size = rt_device_read(record.device, 0, record.buffer, WR_BUFFER_SIZE); + if (size) + { + fwrite(record.buffer, size, 1, record.fp); + total_length += size; + } + + /* recive stop event */ + if (rt_event_recv(record.event, RECORD_EVENT_STOP, + RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, + RT_WAITING_NO, &recv_evt) == RT_EOK) + { + + /* re-write wav header */ + wavheader_init(&wav, record.info.samplerate, record.info.channels, total_length); + fseek(record.fp, 0, SEEK_SET); + wavheader_write(&wav, record.fp); + wavrecorder_close(&record); + + LOG_D("total_length = %d", total_length); + + /* ack event */ + rt_completion_done(&record.ack); + record.activated = RT_FALSE; + + break; + } + } + + return; +} + +rt_err_t wavrecorder_start(struct wavrecord_info *info) +{ + if (record.activated != RT_TRUE) + { + rt_thread_t tid; + + if (record.info.uri) + rt_free(record.info.uri); + record.info.uri = rt_strdup(info->uri); + + record.info.samplerate = info->samplerate; + record.info.channels = info->channels; + record.info.samplefmt = info->samplefmt; + + tid = rt_thread_create("wav_r", wavrecord_entry, RT_NULL, 2048, 19, 20); + if (tid) + rt_thread_startup(tid); + } + + return RT_EOK; +} + +rt_err_t wavrecorder_stop(void) +{ + if (record.activated == RT_TRUE) + { + rt_completion_init(&record.ack); + rt_event_send(record.event, RECORD_EVENT_STOP); + rt_completion_wait(&record.ack, RT_WAITING_FOREVER); + } + + return RT_EOK; +} + +rt_bool_t wavrecorder_is_actived(void) +{ + return record.activated; +} diff --git a/src/wavrecorder_cmd.c b/src/wavrecorder_cmd.c new file mode 100644 index 0000000..3a549f4 --- /dev/null +++ b/src/wavrecorder_cmd.c @@ -0,0 +1,132 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Date Author Notes + * 2019-07-16 Zero-Free first implementation + */ + +#include +#include +#include +#include + +#include + +enum WAVRECORDER_ACTTION +{ + WAVRECORDER_ACTION_HELP = 0, + WAVRECORDER_ACTION_START = 1, + WAVRECORDER_ACTION_STOP = 2, +}; + +struct wavrecord_args +{ + int action; + char *file; + rt_uint16_t samplerate; + rt_uint16_t channels; + rt_uint16_t samplefmt; +}; + +static struct optparse_long opts[] = +{ + {"help", 'h', OPTPARSE_NONE }, /* 帮助 */ + {"start", 's', OPTPARSE_REQUIRED}, /* 开始录音 */ + {"stop", 't', OPTPARSE_NONE }, /* 停止录音 */ + { NULL, 0, OPTPARSE_NONE } +}; + +static void usage(void) +{ + rt_kprintf("usage: wavrecord [option] [target] ...\n\n"); + rt_kprintf("usage options:\n"); + rt_kprintf(" -h, --help Print defined help message.\n"); + rt_kprintf(" -s file --start=file \n"); + rt_kprintf(" record wav music to filesystem.\n"); + rt_kprintf(" -t, --stop Stop record.\n"); +} + +int wavrecord_args_prase(int argc, char *argv[], struct wavrecord_args *record_args) +{ + int ch; + int option_index; + struct optparse options; + rt_err_t result = RT_EOK; + + if (argc == 1) + { + record_args->action = WAVRECORDER_ACTION_HELP; + return RT_EOK; + } + + /* Parse cmd */ + optparse_init(&options, argv); + while ((ch = optparse_long(&options, opts, &option_index)) != -1) + { + switch (ch) + { + case 'h': + record_args->action = WAVRECORDER_ACTION_HELP; + break; + + case 's': + record_args->action = WAVRECORDER_ACTION_START; + record_args->file = options.optarg; + record_args->samplerate = ((argv[3] == RT_NULL) ? 8000 : atoi(argv[3])); + record_args->channels = ((argv[4] == RT_NULL) ? 2 : atoi(argv[4])); + record_args->samplefmt = ((argv[5] == RT_NULL) ? 16 : atoi(argv[5])); + break; + + case 't': + record_args->action = WAVRECORDER_ACTION_STOP; + break; + + default: + result = -RT_EINVAL; + break; + } + } + + return result; +} + +int wav_recorder(int argc, char *argv[]) +{ + int result = RT_EOK; + struct wavrecord_args record_args = {0}; + struct wavrecord_info info = {0}; + + result = wavrecord_args_prase(argc, argv, &record_args); + if (result != RT_EOK) + { + usage(); + return result; + } + + switch (record_args.action) + { + case WAVRECORDER_ACTION_HELP: + usage(); + break; + + case WAVRECORDER_ACTION_START: + info.uri = record_args.file; + info.samplerate = record_args.samplerate; + info.channels = record_args.channels; + info.samplefmt = record_args.samplefmt; + wavrecorder_start(&info); + break; + + case WAVRECORDER_ACTION_STOP: + wavrecorder_stop(); + break; + + default: + result = -RT_ERROR; + break; + } + + return result; +} + +MSH_CMD_EXPORT_ALIAS(wav_recorder, wavrecord, record wav music); From a9ef816f006e8114da93eb35e80a9e537dd21058 Mon Sep 17 00:00:00 2001 From: EvalZero Date: Wed, 17 Jul 2019 17:23:46 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E3=80=90=E6=9B=B4=E6=96=B0=E3=80=91README?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 27573c8..aac694e 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,36 @@ wavplayer package 遵循 Apache 2.0 许可,详见 `LICENSE` 文件。 ## 2. 使用 -wavplayer 的常用功能已经导出到 Finsh 命令行,下面是使用示例。 +wavplayer 的常用功能已经导出到 Finsh 命令行,以便开发者测试和使用。命令主要分为播放和录音两个类别,分别提供不同的功能。 + +**播放命令提供的功能如下 ** + +```shell +msh />wavplay -help +usage: wavplay [option] [target] ... + +usage options: + -h, --help Print defined help message. + -s URI, --start=URI Play wav music with URI(local files). + -t, --stop Stop playing music. + -p, --pause Pause the music. + -r, --resume Resume the music. + -v lvl, --volume=lvl Change the volume(0~99). + -d, --dump Dump play relevant information. +``` + +**录音命令提供的功能如下** + +```shell +msh />wavrecord -h +usage: wavrecord [option] [target] ... + +usage options: + -h, --help Print defined help message. + -s file --start=file + record wav music to filesystem. + -t, --stop Stop record. +``` ### 2.1 播放功能