450 lines
14 KiB
C
450 lines
14 KiB
C
|
/* voicememotest.c - native voicememo test application
|
||
|
*
|
||
|
* Based on native pcm test application platform/system/extras/sound/playwav.c
|
||
|
*
|
||
|
* Copyright (C) 2008 The Android Open Source Project
|
||
|
* Copyright (c) 2009, The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <stdint.h>
|
||
|
#include <sys/mman.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <unistd.h>
|
||
|
#include <string.h>
|
||
|
#include <sys/ioctl.h>
|
||
|
#include <linux/msm_audio_voicememo.h>
|
||
|
#include <pthread.h>
|
||
|
#include "audiotest_def.h"
|
||
|
|
||
|
#define VOICEMEMO_DEVICE_NODE "/dev/msm_voicememo"
|
||
|
|
||
|
struct qcp_header {
|
||
|
/* RIFF Section */
|
||
|
char riff[4];
|
||
|
unsigned int s_riff;
|
||
|
char qlcm[4];
|
||
|
|
||
|
/* Format chunk */
|
||
|
char fmt[4];
|
||
|
unsigned int s_fmt;
|
||
|
char mjr;
|
||
|
char mnr;
|
||
|
unsigned int data1; /* UNIQUE ID of the codec */
|
||
|
unsigned short data2;
|
||
|
unsigned short data3;
|
||
|
char data4[8];
|
||
|
unsigned short ver; /* Codec Info */
|
||
|
char name[80];
|
||
|
unsigned short abps; /* average bits per sec of the codec */
|
||
|
unsigned short bytes_per_pkt;
|
||
|
unsigned short samp_per_block;
|
||
|
unsigned short samp_per_sec;
|
||
|
unsigned short bits_per_samp;
|
||
|
unsigned char vr_num_of_rates; /* Rate Header fmt info */
|
||
|
unsigned char rvd1[3];
|
||
|
unsigned short vr_bytes_per_pkt[8];
|
||
|
unsigned int rvd2[5];
|
||
|
|
||
|
/* Vrat chunk */
|
||
|
unsigned char vrat[4];
|
||
|
unsigned int s_vrat;
|
||
|
unsigned int v_rate;
|
||
|
unsigned int size_in_pkts;
|
||
|
|
||
|
/* Data chunk */
|
||
|
unsigned char data[4];
|
||
|
unsigned int s_data;
|
||
|
} __attribute__ ((packed));
|
||
|
|
||
|
/* Common part */
|
||
|
static struct qcp_header append_header = {
|
||
|
{'R', 'I', 'F', 'F'}, 0, {'Q', 'L', 'C', 'M'}, /* Riff */
|
||
|
{'f', 'm', 't', ' '}, 150, 1, 0, 0, 0, 0,{0}, 0, {0},0,0,160,8000,16,0,{0},{0},{0}, /* Fmt */
|
||
|
{'v','r','a','t'}, 0, 0, 0, /* Vrat */
|
||
|
{'d','a','t','a'},0 /* Data */
|
||
|
};
|
||
|
|
||
|
static char amr_header[6] = { '#','!','A','M','R','\n'};
|
||
|
|
||
|
#define QCP_HEADER_SIZE sizeof(struct qcp_header)
|
||
|
#define AMR_HEADER_SIZE sizeof(amr_header)
|
||
|
|
||
|
static int start;
|
||
|
static struct msm_audio_voicememo_config scfg;
|
||
|
|
||
|
static void create_qcp_header(int Datasize, int Frames)
|
||
|
{
|
||
|
append_header.s_riff = Datasize + QCP_HEADER_SIZE - 8; /* exclude riff id and size field */
|
||
|
if( scfg.capability == 4 ) { /* QCELP 13K */
|
||
|
printf("QCELP 13K header\n");
|
||
|
append_header.data1 = 0x5E7F6D41;
|
||
|
append_header.data2 = 0xB115;
|
||
|
append_header.data3 = 0x11D0;
|
||
|
append_header.data4[0] = 0xBA;
|
||
|
append_header.data4[1] = 0x91;
|
||
|
append_header.data4[2] = 0x00;
|
||
|
append_header.data4[3] = 0x80;
|
||
|
append_header.data4[4] = 0x5F;
|
||
|
append_header.data4[5] = 0xB4;
|
||
|
append_header.data4[6] = 0xB9;
|
||
|
append_header.data4[7] = 0x7E;
|
||
|
append_header.ver = 0x0002;
|
||
|
memcpy(append_header.name, "Qcelp 13K", 9);
|
||
|
append_header.abps = 13000;
|
||
|
append_header.bytes_per_pkt = 35;
|
||
|
append_header.vr_num_of_rates = 5;
|
||
|
append_header.vr_bytes_per_pkt[0] = 0x0422;
|
||
|
append_header.vr_bytes_per_pkt[1] = 0x0310;
|
||
|
append_header.vr_bytes_per_pkt[2] = 0x0207;
|
||
|
append_header.vr_bytes_per_pkt[3] = 0x0103;
|
||
|
append_header.s_vrat = 0x00000008;
|
||
|
append_header.v_rate = 0x00000001;
|
||
|
append_header.size_in_pkts = Frames;
|
||
|
} else if ( scfg.capability == 8) { /* EVRC */
|
||
|
printf("EVRC header\n");
|
||
|
append_header.data1 = 0xe689d48d;
|
||
|
append_header.data2 = 0x9076;
|
||
|
append_header.data3 = 0x46b5;
|
||
|
append_header.data4[0] = 0x91;
|
||
|
append_header.data4[1] = 0xef;
|
||
|
append_header.data4[2] = 0x73;
|
||
|
append_header.data4[3] = 0x6a;
|
||
|
append_header.data4[4] = 0x51;
|
||
|
append_header.data4[5] = 0x00;
|
||
|
append_header.data4[6] = 0xce;
|
||
|
append_header.data4[7] = 0xb4;
|
||
|
append_header.ver = 0x0001;
|
||
|
memcpy(append_header.name, "TIA IS-127 Enhanced Variable Rate Codec, Speech Service Option 3", 64);
|
||
|
append_header.abps = 9600;
|
||
|
append_header.bytes_per_pkt = 23;
|
||
|
append_header.vr_num_of_rates = 4;
|
||
|
append_header.vr_bytes_per_pkt[0] = 0x0416;
|
||
|
append_header.vr_bytes_per_pkt[1] = 0x030a;
|
||
|
append_header.vr_bytes_per_pkt[2] = 0x0200;
|
||
|
append_header.vr_bytes_per_pkt[3] = 0x0102;
|
||
|
append_header.s_vrat = 0x00000008;
|
||
|
append_header.v_rate = 0x00000001;
|
||
|
append_header.size_in_pkts = Frames;
|
||
|
} else if( scfg.capability == 64 ) { /* AMRNB */
|
||
|
printf("AMRNB header\n");
|
||
|
append_header.data1 = 0x6aa8e053;
|
||
|
append_header.data2 = 0x474f;
|
||
|
append_header.data3 = 0xbd46;
|
||
|
append_header.data4[0] = 0x8a;
|
||
|
append_header.data4[1] = 0xfa;
|
||
|
append_header.data4[2] = 0xac;
|
||
|
append_header.data4[3] = 0xf2;
|
||
|
append_header.data4[4] = 0x32;
|
||
|
append_header.data4[5] = 0x82;
|
||
|
append_header.data4[6] = 0x73;
|
||
|
append_header.data4[7] = 0xbd;
|
||
|
append_header.ver = 0x0001;
|
||
|
memcpy(append_header.name, "AMR-NB ", 9);
|
||
|
append_header.abps = 0x9c31;
|
||
|
append_header.bytes_per_pkt = 32;
|
||
|
append_header.vr_num_of_rates = 8;
|
||
|
append_header.vr_bytes_per_pkt[0] = 0x081f;
|
||
|
append_header.vr_bytes_per_pkt[1] = 0x071b;
|
||
|
append_header.vr_bytes_per_pkt[2] = 0x0614;
|
||
|
append_header.vr_bytes_per_pkt[3] = 0x0513;
|
||
|
append_header.vr_bytes_per_pkt[4] = 0x0411;
|
||
|
append_header.vr_bytes_per_pkt[4] = 0x040f;
|
||
|
append_header.vr_bytes_per_pkt[5] = 0x030d;
|
||
|
append_header.vr_bytes_per_pkt[6] = 0x020c;
|
||
|
append_header.s_vrat = 0x00000008;
|
||
|
append_header.v_rate = 0x00000001;
|
||
|
append_header.size_in_pkts = Frames;
|
||
|
}
|
||
|
append_header.s_data = Datasize;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static int voicememo_start(struct audtest_config *clnt_config)
|
||
|
{
|
||
|
int afd, fd;
|
||
|
unsigned char tmp;
|
||
|
unsigned char buf[1024];
|
||
|
unsigned sz,total;
|
||
|
int readcnt,writecnt;
|
||
|
struct msm_audio_config cfg;
|
||
|
struct msm_audio_voicememo_config gcfg;
|
||
|
struct msm_audio_stats stats;
|
||
|
memset(&stats,0,sizeof(stats));
|
||
|
memset(&cfg,0,sizeof(cfg));
|
||
|
memset(&scfg,0,sizeof(scfg));
|
||
|
|
||
|
fd = open(clnt_config->file_name, O_CREAT | O_RDWR, 0666);
|
||
|
|
||
|
if (fd < 0) {
|
||
|
printf("Unable to create output file = %s\n",
|
||
|
clnt_config->file_name);
|
||
|
goto file_err;
|
||
|
}
|
||
|
else
|
||
|
printf("file created =%s\n",clnt_config->file_name);
|
||
|
|
||
|
/* Open Device Node */
|
||
|
afd = open(VOICEMEMO_DEVICE_NODE, O_RDWR);
|
||
|
if (afd < 0) {
|
||
|
printf("Unable to open audio device = %s\n",
|
||
|
VOICEMEMO_DEVICE_NODE);
|
||
|
goto device_err;
|
||
|
}
|
||
|
|
||
|
/* Config param */
|
||
|
if(ioctl(afd, AUDIO_GET_CONFIG, &cfg)) {
|
||
|
printf(" Error getting buf config param AUDIO_GET_CONFIG \n");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
if (ioctl(afd, AUDIO_GET_VOICEMEMO_CONFIG, &gcfg)) {
|
||
|
printf("Error: AUDIO_GET_VOICEMEMO_CONFIG failed\n");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG_LOCAL
|
||
|
printf("Default rec_type = 0x%8x\n",gcfg.rec_type);
|
||
|
printf("Default rec_interval_ms = 0x%8x\n",gcfg.rec_interval_ms);
|
||
|
printf("Default auto_stop_ms = 0x%8x\n",gcfg.auto_stop_ms);
|
||
|
printf("Default capability = 0x%8x\n",gcfg.capability);
|
||
|
printf("Default max_rate = 0x%8x\n",gcfg.max_rate);
|
||
|
printf("Default min_rate = 0x%8x\n",gcfg.min_rate);
|
||
|
printf("Default frame_format = 0x%8x\n",gcfg.frame_format);
|
||
|
printf("Default dtx_enable = 0x%8x\n",gcfg.dtx_enable);
|
||
|
printf("Default data_req_ms = 0x%8x\n",gcfg.data_req_ms);
|
||
|
#endif
|
||
|
/* Store handle for commands pass*/
|
||
|
clnt_config->private_data = (void *) afd;
|
||
|
|
||
|
start = 0;
|
||
|
do {
|
||
|
usleep(1000000);
|
||
|
} while(!start);
|
||
|
|
||
|
sz = cfg.buffer_size;
|
||
|
total = 0;
|
||
|
|
||
|
/* Set Via config param */
|
||
|
if (ioctl(afd, AUDIO_SET_VOICEMEMO_CONFIG, &scfg)) {
|
||
|
printf("Error: AUDIO_SET_VOICEMEMO_CONFIG failed\n");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
if (ioctl(afd, AUDIO_GET_VOICEMEMO_CONFIG, &gcfg)) {
|
||
|
printf("Error: AUDIO_GET_VOICEMEMO_CONFIG failed\n");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG_LOCAL
|
||
|
printf("After set rec_type = 0x%8x\n",gcfg.rec_type);
|
||
|
printf("After set rec_interval_ms = 0x%8x\n",gcfg.rec_interval_ms);
|
||
|
printf("After set auto_stop_ms = 0x%8x\n",gcfg.auto_stop_ms);
|
||
|
printf("After set capability = 0x%8x\n",gcfg.capability);
|
||
|
printf("After set max_rate = 0x%8x\n",gcfg.max_rate);
|
||
|
printf("After set min_rate = 0x%8x\n",gcfg.min_rate);
|
||
|
printf("After set frame_format = 0x%8x\n",gcfg.frame_format);
|
||
|
printf("After set dtx_enable = 0x%8x\n",gcfg.dtx_enable);
|
||
|
printf("After set data_req_ms = 0x%8x\n",gcfg.data_req_ms);
|
||
|
#endif
|
||
|
fcntl(0, F_SETFL, O_NONBLOCK);
|
||
|
ioctl(afd, AUDIO_START, 0);
|
||
|
|
||
|
printf("Voice Memo started \n");
|
||
|
|
||
|
ioctl(afd, AUDIO_GET_STATS, &stats);
|
||
|
#ifdef DEBUG_LOCAL
|
||
|
printf("\n read_bytes = %d, read_frame_counts = %d\n",stats.byte_count, stats.sample_count);
|
||
|
#endif
|
||
|
printf("\n*** RECORDING - HIT ENTER TO STOP ***\n");
|
||
|
if( scfg.frame_format == 3) /* QCP file */
|
||
|
lseek(fd, QCP_HEADER_SIZE, SEEK_SET);
|
||
|
else if ( scfg.frame_format == 4) /* AMR file */
|
||
|
lseek(fd, AMR_HEADER_SIZE, SEEK_SET);
|
||
|
|
||
|
for (;;) {
|
||
|
/* Look for Enter key to terminate */
|
||
|
while (read(0, &tmp, 1) == 1) {
|
||
|
if ((tmp == 13) || (tmp == 10)) goto done;
|
||
|
}
|
||
|
|
||
|
readcnt = read(afd, buf, sz);
|
||
|
#ifdef DEBUG_LOCAL
|
||
|
printf(" Read bytes = %d \n", readcnt);
|
||
|
#endif
|
||
|
if (readcnt <= 0) {
|
||
|
printf("cannot read buffer error code =0x%8x", readcnt);
|
||
|
goto fail;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
writecnt = write(fd, buf, readcnt);
|
||
|
if (writecnt <= 0) {
|
||
|
printf("cannot write buffer error code =0x%8x", writecnt);
|
||
|
goto fail;
|
||
|
}
|
||
|
#ifdef DEBUG_LOCAL
|
||
|
printf(" Written bytes = %d \n", writecnt);
|
||
|
#endif
|
||
|
}
|
||
|
total+=writecnt;
|
||
|
}
|
||
|
done:
|
||
|
ioctl(afd, AUDIO_GET_STATS, &stats);
|
||
|
printf("\n read_bytes = %d, read_frame_counts = %d\n",stats.byte_count, stats.sample_count);
|
||
|
ioctl(afd, AUDIO_STOP, 0);
|
||
|
if( scfg.frame_format == 3) { /* QCP file */
|
||
|
create_qcp_header(stats.byte_count, stats.sample_count);
|
||
|
lseek(fd, 0, SEEK_SET);
|
||
|
write(fd, (char *)&append_header, QCP_HEADER_SIZE);
|
||
|
} else if ( scfg.frame_format == 4) { /* AMR file */
|
||
|
lseek(fd, 0, SEEK_SET);
|
||
|
write(fd, (char *)&amr_header, AMR_HEADER_SIZE);
|
||
|
}
|
||
|
#ifdef DEBUG_LOCAL
|
||
|
printf("Bytes recorded %d\n", total);
|
||
|
#endif
|
||
|
printf("Voice Memo stopped \n");
|
||
|
close(afd);
|
||
|
close(fd);
|
||
|
return 0;
|
||
|
fail:
|
||
|
close(afd);
|
||
|
device_err:
|
||
|
close(fd);
|
||
|
unlink(clnt_config->file_name);
|
||
|
file_err:
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
void *voicememo_thread(void *arg)
|
||
|
{
|
||
|
struct audiotest_thread_context *context =
|
||
|
(struct audiotest_thread_context *)arg;
|
||
|
int ret_val;
|
||
|
|
||
|
ret_val = voicememo_start(&context->config);
|
||
|
free_context(context);
|
||
|
pthread_exit((void *)ret_val);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
int voicememo_read_params(void)
|
||
|
{
|
||
|
struct audiotest_thread_context *context;
|
||
|
char *token;
|
||
|
int ret_val = 0;
|
||
|
|
||
|
if ((context = get_free_context()) == NULL) {
|
||
|
ret_val = -1;
|
||
|
} else {
|
||
|
#ifdef _ANDROID_
|
||
|
context->config.file_name = "/data/sample.raw";
|
||
|
#else
|
||
|
context->config.file_name = "/tmp/sample.raw";
|
||
|
#endif
|
||
|
context->type = AUDIOTEST_TEST_MOD_VOICEMEMO;
|
||
|
token = strtok(NULL, " ");
|
||
|
while (token != NULL) {
|
||
|
if (!memcmp(token, "-id=", (sizeof("-id=") - 1))) {
|
||
|
context->cxt_id =
|
||
|
atoi(&token[sizeof("-id=") - 1]);
|
||
|
} else if (!memcmp(token, "-out=",
|
||
|
(sizeof("-out=") - 1))) {
|
||
|
context->config.file_name = token + (sizeof("-out=")-1);
|
||
|
}
|
||
|
token = strtok(NULL, " ");
|
||
|
}
|
||
|
pthread_create(&context->thread, NULL,
|
||
|
voicememo_thread, (void *)context);
|
||
|
}
|
||
|
|
||
|
return ret_val;
|
||
|
|
||
|
}
|
||
|
|
||
|
int voicememo_control_handler(void *private_data)
|
||
|
{
|
||
|
int drvfd , ret_val = 0;
|
||
|
char *token;
|
||
|
|
||
|
token = strtok(NULL, " ");
|
||
|
if ((private_data != NULL) && (token != NULL)) {
|
||
|
drvfd = (int) private_data;
|
||
|
if (!memcmp(token, "-cmd=", (sizeof("-cmd=") - 1))) {
|
||
|
token = &token[sizeof("-cmd=") - 1];
|
||
|
printf("%s: cmd %s\n", __FUNCTION__, token);
|
||
|
if (!strcmp(token, "pause")) {
|
||
|
ioctl(drvfd, AUDIO_PAUSE, 1);
|
||
|
} else if (!strcmp(token, "resume")) {
|
||
|
ioctl(drvfd, AUDIO_PAUSE, 0);
|
||
|
} else if (!strcmp(token, "config")) {
|
||
|
token = strtok(NULL, " ");
|
||
|
if (!memcmp(token, "-rec=",
|
||
|
(sizeof("-rec=") - 1)))
|
||
|
scfg.rec_type =
|
||
|
atoi(&token[sizeof("-rec=") - 1]);
|
||
|
token = strtok(NULL, " ");
|
||
|
if (!memcmp(token, "-cap=",
|
||
|
(sizeof("-cap=") - 1)))
|
||
|
scfg.capability =
|
||
|
atoi(&token[sizeof("-cap=") - 1]);
|
||
|
token = strtok(NULL, " ");
|
||
|
if (!memcmp(token, "-maxr=",
|
||
|
(sizeof("-maxr=") - 1)))
|
||
|
scfg.max_rate =
|
||
|
atoi(&token[sizeof("-maxr=") - 1]);
|
||
|
token = strtok(NULL, " ");
|
||
|
if (!memcmp(token, "-minr=",
|
||
|
(sizeof("-minr=") - 1)))
|
||
|
scfg.min_rate =
|
||
|
atoi(&token[sizeof("-minr=") - 1]);
|
||
|
token = strtok(NULL, " ");
|
||
|
if (!memcmp(token, "-frame=",
|
||
|
(sizeof("-frame=") - 1)))
|
||
|
scfg.frame_format =
|
||
|
atoi(&token[sizeof("-frame=") - 1]);
|
||
|
token = strtok(NULL, " ");
|
||
|
if (!memcmp(token, "-datams=",
|
||
|
(sizeof("-datams=") - 1)))
|
||
|
scfg.data_req_ms =
|
||
|
atoi(&token[sizeof("-datams=") - 1]);
|
||
|
scfg.rec_interval_ms = 0;
|
||
|
scfg.auto_stop_ms = 0;
|
||
|
scfg.dtx_enable = 0;
|
||
|
start = 1;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
ret_val = -1;
|
||
|
}
|
||
|
return ret_val;
|
||
|
}
|
||
|
|
||
|
const char *voicememo_help_txt = "Record voice in QCP/AMR file format: type \n\
|
||
|
echo \"voicememo -id=xxx -out=path_of_file \" > /tmp/audio_test \n\
|
||
|
Supported control command: pause, resume, config \n\
|
||
|
examples: \n\
|
||
|
echo \"voicememo -id=xxx -out=path_of_file \" > /tmp/audio_test \n\
|
||
|
echo \"control_cmd -id=xxx -cmd=pause\" > /tmp/audio_test \n\
|
||
|
echo \"control_cmd -id=xxx -cmd=resume\" > /tmp/audio_test \n\
|
||
|
echo \"control_cmd -id=xxx cmd=config -rec=aaa -cap=bbb -maxr=ccc -minr=ddd -frame=eee -datams=fff \" > /tmp/audio_test \n";
|
||
|
|
||
|
void voicememo_help_menu(void)
|
||
|
{
|
||
|
printf("%s\n", voicememo_help_txt);
|
||
|
}
|