348 lines
8.3 KiB
C
348 lines
8.3 KiB
C
/**
|
||
* Copyright (2017) Baidu Inc. 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.
|
||
*/
|
||
// File: main.c
|
||
// Auth: Su Hao (suhao@baidu.com)
|
||
//
|
||
// Desc: Encode the WAV to speex.
|
||
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <getopt.h>
|
||
#include <libgen.h>
|
||
|
||
#include "lightduer_types.h"
|
||
#include "lightduer_speex.h"
|
||
|
||
#define ERROR(...) fprintf(stderr, "/E %s(%d): ", __FILE__, __LINE__), fprintf(stderr, __VA_ARGS__), fprintf(stderr, "\n")
|
||
#define DEBUG(...) //fprintf(stdout, "/D %s(%d): ", __FILE__, __LINE__), fprintf(stdout, __VA_ARGS__), fprintf(stdout, "\n")
|
||
|
||
#define PRINT(...) fprintf(stdout, __VA_ARGS__)
|
||
|
||
#define SUFFIX ".spx"
|
||
|
||
typedef enum _duer_error_code_enum {
|
||
DUER_NO_ERROR,
|
||
DUER_ERR_ON_INPUT = -1,
|
||
DUER_ERR_INVALID_ARGUMENT = -2,
|
||
DUER_ERR_INVALID_FILE = -3,
|
||
DUER_ERR_CANNOT_OPEN_FILE = -4,
|
||
DUER_ERR_INVALID_PCM_FILE = -5,
|
||
DUER_ERR_INVALID_PARAM = -6,
|
||
DUER_ERR_MEMORY_OVERFLOW = -7,
|
||
} duer_errcode_e;
|
||
|
||
typedef struct _duer_wav_header_s {
|
||
|
||
// Main Chunk
|
||
char _chunk_id[4]; // "RIFF"标记
|
||
int _chunk_size; // 文件大小
|
||
char _format[4]; // "WAVE"标记
|
||
|
||
// Sub Chunk
|
||
char _subchunk_id[4]; // "fmt "标记
|
||
int _subchunk_size; // 预留
|
||
short _audio_format; // 音频格式,10H为PCM数据
|
||
short _num_channels; // 声道数
|
||
int _sample_rate; // 采样率
|
||
int _byte_rate; // 波形音频数据传送速率
|
||
short _block_align; // 数据块的调整数
|
||
short _bits_per_sample; // 每个样本占用的数据位大小
|
||
|
||
} duer_wavhdr_t;
|
||
|
||
typedef struct _duer_wav_chunk_s {
|
||
|
||
char _id[4]; // 标记
|
||
int _size; // 大小
|
||
|
||
} duer_wavchk_t;
|
||
|
||
static FILE *g_file_out = NULL;
|
||
|
||
void usage(const char *command)
|
||
{
|
||
if (command == NULL) {
|
||
command = "speexenc";
|
||
}
|
||
|
||
PRINT("%s INPUT [OUTPUT]\n\n", basename((char *)command));
|
||
}
|
||
|
||
void *bcamem_alloc(duer_context ctx, duer_size_t size)
|
||
{
|
||
return malloc(size);
|
||
}
|
||
|
||
void *bcamem_realloc(duer_context ctx, void *ptr, duer_size_t size)
|
||
{
|
||
return realloc(ptr, size);
|
||
}
|
||
|
||
void bcamem_free(duer_context ctx, void *ptr)
|
||
{
|
||
free(ptr);
|
||
}
|
||
|
||
int wav_check_header(const duer_wavhdr_t *header)
|
||
{
|
||
int rs = DUER_ERR_INVALID_PCM_FILE;
|
||
|
||
if (header == NULL) {
|
||
ERROR("Invalid parameter!!!");
|
||
rs = DUER_ERR_INVALID_PARAM;
|
||
goto exit;
|
||
}
|
||
|
||
if (strncmp("RIFF", header->_chunk_id, 4) != 0) {
|
||
ERROR("No RIFF flag!!!");
|
||
goto exit;
|
||
}
|
||
|
||
if (header->_chunk_size <= 0) {
|
||
ERROR("Invalid PCM file size: %d", header->_chunk_size);
|
||
goto exit;
|
||
}
|
||
|
||
if (strncmp("WAVE", header->_format, 4) != 0) {
|
||
ERROR("No WAVE flag!!!");
|
||
goto exit;
|
||
}
|
||
|
||
if (strncmp("fmt ", header->_subchunk_id, 4) != 0) {
|
||
ERROR("No 'fmt ' flag!!!");
|
||
goto exit;
|
||
}
|
||
|
||
if (header->_audio_format != 0x01) {
|
||
ERROR("The WAV is not PCM!!! _audio_format = %d", header->_audio_format);
|
||
goto exit;
|
||
}
|
||
|
||
if (header->_num_channels != 0x01) {
|
||
ERROR("Support SINGLE CHANNEL only!!! _num_channels = %d", header->_num_channels);
|
||
goto exit;
|
||
}
|
||
|
||
if (header->_sample_rate != 8000 && header->_sample_rate != 16000) {
|
||
ERROR("Support SAMPLE RATE: 8000 & 16000!!! _sample_rate = %d", header->_sample_rate);
|
||
goto exit;
|
||
}
|
||
|
||
if (header->_bits_per_sample != (header->_sample_rate / 1000)) {
|
||
ERROR("Support BITS/SAMPLE: <%d> with SAMPLE RATE: <%d>!!! _bits_per_sample = %d", header->_sample_rate / 1000, header->_sample_rate, header->_bits_per_sample);
|
||
goto exit;
|
||
}
|
||
|
||
rs = DUER_NO_ERROR;
|
||
|
||
exit:
|
||
|
||
return rs;
|
||
}
|
||
|
||
int wav_read_chunk(FILE *file, duer_wavchk_t *chunk)
|
||
{
|
||
|
||
int rs = DUER_ERR_INVALID_PARAM;
|
||
|
||
if (file == NULL || chunk == NULL) {
|
||
ERROR("Invalid parameters!!!");
|
||
return rs;
|
||
}
|
||
|
||
do {
|
||
rs = fread(chunk->_id, 1, sizeof(chunk->_id), file);
|
||
if (rs != sizeof(chunk->_id)) {
|
||
ERROR("Can't read the Chunk tag: %d", rs);
|
||
rs = DUER_ERR_INVALID_PCM_FILE;
|
||
break;
|
||
}
|
||
|
||
rs = fread(&(chunk->_size), 1, sizeof(chunk->_size), file);
|
||
if (rs != sizeof(chunk->_size)) {
|
||
ERROR("Can't read the Chunk size: %d", rs);
|
||
rs = DUER_ERR_INVALID_PCM_FILE;
|
||
break;
|
||
}
|
||
|
||
if (strncmp("data", chunk->_id, 4) == 0) {
|
||
rs = DUER_NO_ERROR;
|
||
break;
|
||
}
|
||
|
||
rs = fseek(file, chunk->_size, SEEK_CUR);
|
||
if (rs != 0) {
|
||
ERROR("Can't seek to <%d> from current!!!", rs);
|
||
rs = DUER_ERR_INVALID_PCM_FILE;
|
||
break;
|
||
}
|
||
} while (1);
|
||
|
||
return rs;
|
||
}
|
||
|
||
static void speex_encode_callback(const void *data, size_t size)
|
||
{
|
||
if (g_file_out) {
|
||
int rs = fwrite(data, 1, size, g_file_out);
|
||
DEBUG("The write size = %d", rs);
|
||
}
|
||
}
|
||
|
||
int pcm2spx(const char *pcm, const char *spx)
|
||
{
|
||
int rs = DUER_NO_ERROR;
|
||
|
||
FILE *f_in = NULL;
|
||
FILE *f_out = NULL;
|
||
duer_wavhdr_t header;
|
||
duer_wavchk_t chunk;
|
||
int frame_size = 0;
|
||
char frame[1024];
|
||
duer_speex_handler speex = NULL;
|
||
|
||
f_in = fopen(pcm, "rb");
|
||
if (f_in == NULL) {
|
||
ERROR("Can't open the PCM file: %s", pcm);
|
||
rs = DUER_ERR_CANNOT_OPEN_FILE;
|
||
goto exit;
|
||
}
|
||
|
||
f_out = fopen(spx, "wb");
|
||
if (f_out == NULL) {
|
||
ERROR("Can't open the SPEEX file: %s", spx);
|
||
rs = DUER_ERR_CANNOT_OPEN_FILE;
|
||
goto exit;
|
||
}
|
||
g_file_out = f_out;
|
||
|
||
rs = fread(&header, 1, sizeof(header), f_in);
|
||
if (rs != sizeof(header)) {
|
||
ERROR("Invalid PCM file: %s", pcm);
|
||
rs = DUER_ERR_INVALID_PCM_FILE;
|
||
goto exit;
|
||
}
|
||
|
||
DEBUG("The header size = %u", rs);
|
||
|
||
rs = wav_check_header(&header);
|
||
if (rs < DUER_NO_ERROR) {
|
||
goto exit;
|
||
}
|
||
|
||
rs = wav_read_chunk(f_in, &chunk);
|
||
if (rs < DUER_NO_ERROR) {
|
||
goto exit;
|
||
}
|
||
|
||
speex = duer_speex_create(header._sample_rate);
|
||
if (speex == NULL) {
|
||
ERROR("Create speex failed!!! rate = %d", header._sample_rate);
|
||
goto exit;
|
||
}
|
||
|
||
frame_size = header._sample_rate * 20 / 1000 * header._bits_per_sample / 8;
|
||
DEBUG("The frame size = %u", frame_size);
|
||
do {
|
||
rs = fread(frame, 1, frame_size, f_in);
|
||
DEBUG("The read size = %u", rs);
|
||
|
||
if (rs > 0) {
|
||
duer_speex_encode(speex, frame, rs, speex_encode_callback);
|
||
}
|
||
} while (rs > 0);
|
||
|
||
duer_speex_destroy(speex);
|
||
|
||
exit:
|
||
|
||
g_file_out = NULL;
|
||
|
||
if (f_in) {
|
||
fclose(f_in);
|
||
}
|
||
|
||
if (f_out) {
|
||
fclose(f_out);
|
||
}
|
||
|
||
return rs;
|
||
}
|
||
|
||
int main(int args, char *argv[])
|
||
{
|
||
int rs = 0;
|
||
|
||
const char *pcm = NULL;
|
||
const char *spx = NULL;
|
||
const char *suffix = SUFFIX;
|
||
char *temp = NULL;
|
||
int i;
|
||
|
||
baidu_ca_memory_init(
|
||
NULL,
|
||
bcamem_alloc,
|
||
bcamem_realloc,
|
||
bcamem_free);
|
||
|
||
if (args < 2) {
|
||
ERROR("You need input a PCM file!!!");
|
||
rs = DUER_ERR_ON_INPUT;
|
||
goto exit;
|
||
}
|
||
|
||
i = 1;
|
||
while (i < args) {
|
||
DEBUG("%d ==> %s", i, argv[i]);
|
||
if (pcm == NULL) {
|
||
pcm = argv[i];
|
||
} else if (spx == NULL) {
|
||
spx = argv[i];
|
||
} else {
|
||
ERROR("You have specificed the INPUT and OUTPUT files!!!");
|
||
rs = DUER_ERR_INVALID_ARGUMENT;
|
||
goto exit;
|
||
}
|
||
i++;
|
||
}
|
||
|
||
if (spx == NULL) {
|
||
i = strlen(pcm) + strlen(suffix);
|
||
temp = (char *)malloc(i + 1);
|
||
if (temp == NULL) {
|
||
ERROR("Memory overflow!!!");
|
||
rs = DUER_ERR_MEMORY_OVERFLOW;
|
||
goto exit;
|
||
}
|
||
snprintf(temp, i + 1, "%s%s", pcm, suffix);
|
||
spx = temp;
|
||
}
|
||
|
||
rs = pcm2spx(pcm, spx);
|
||
|
||
exit:
|
||
|
||
if (temp != NULL) {
|
||
free(temp);
|
||
}
|
||
|
||
if (rs < DUER_NO_ERROR) {
|
||
usage(argv[0]);
|
||
}
|
||
|
||
return rs;
|
||
}
|