489 lines
17 KiB
C
489 lines
17 KiB
C
/*
|
|
* Copyright (c) 2016, The Linux Foundation. All rights reserved.
|
|
* Not a Contribution.
|
|
*
|
|
* Copyright (C) 2015 The Android Open Source Project *
|
|
* 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.
|
|
*/
|
|
|
|
/* Test app to play audio at the HAL layer */
|
|
|
|
#include <getopt.h>
|
|
#include <pthread.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include "qahw_api.h"
|
|
#include "qahw_defs.h"
|
|
|
|
#define nullptr NULL
|
|
FILE * log_file = NULL;
|
|
const char *log_filename = NULL;
|
|
float vol_level = 0.01;
|
|
|
|
enum {
|
|
FILE_WAV = 1,
|
|
FILE_MP3,
|
|
FILE_AAC,
|
|
FILE_AAC_ADTS
|
|
};
|
|
|
|
typedef enum {
|
|
AAC_LC = 1,
|
|
AAC_HE_V1,
|
|
AAC_HE_V2
|
|
} aac_format_type_t;
|
|
|
|
static pthread_mutex_t write_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
static pthread_cond_t write_cond = PTHREAD_COND_INITIALIZER;
|
|
static pthread_mutex_t drain_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
static pthread_cond_t drain_cond = PTHREAD_COND_INITIALIZER;
|
|
|
|
|
|
int async_callback(qahw_stream_callback_event_t event, void *param,
|
|
void *cookie)
|
|
{
|
|
switch (event) {
|
|
case QAHW_STREAM_CBK_EVENT_WRITE_READY:
|
|
fprintf(log_file, "QAHW_STREAM_CBK_EVENT_DRAIN_READY\n");
|
|
pthread_mutex_lock(&write_lock);
|
|
pthread_cond_signal(&write_cond);
|
|
pthread_mutex_unlock(&write_lock);
|
|
break;
|
|
case QAHW_STREAM_CBK_EVENT_DRAIN_READY:
|
|
fprintf(log_file, "QAHW_STREAM_CBK_EVENT_DRAIN_READY\n");
|
|
pthread_mutex_lock(&drain_lock);
|
|
pthread_cond_signal(&drain_cond);
|
|
pthread_mutex_unlock(&drain_lock);
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int write_to_hal(qahw_stream_handle_t* out_handle, char *data,
|
|
size_t bytes)
|
|
{
|
|
ssize_t ret;
|
|
pthread_mutex_lock(&write_lock);
|
|
qahw_out_buffer_t out_buf;
|
|
|
|
memset(&out_buf,0, sizeof(qahw_out_buffer_t));
|
|
out_buf.buffer = data;
|
|
out_buf.bytes = bytes;
|
|
|
|
ret = qahw_out_write(out_handle, &out_buf);
|
|
if (ret < 0 || ret == bytes) {
|
|
fprintf(log_file, "Writing data to hal failed or full write %ld, %ld\n",
|
|
ret, bytes);
|
|
} else if (ret != bytes) {
|
|
fprintf(log_file, "ret %ld, bytes %ld\n", ret, bytes);
|
|
fprintf(log_file, "Waiting for event write ready\n");
|
|
pthread_cond_wait(&write_cond, &write_lock);
|
|
fprintf(log_file, "out of wait for event write ready\n");
|
|
}
|
|
|
|
pthread_mutex_unlock(&write_lock);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Play audio from a WAV file.
|
|
*
|
|
* Parameters:
|
|
* out_stream: A pointer to the output audio stream.
|
|
* in_file: A pointer to a SNDFILE object.
|
|
* config: A pointer to struct that contains audio configuration data.
|
|
*
|
|
* Returns: An int which has a non-negative number on success.
|
|
*/
|
|
|
|
int play_file(qahw_stream_handle_t* out_handle, FILE* in_file,
|
|
bool is_offload) {
|
|
int rc = 0;
|
|
int offset = 0;
|
|
size_t bytes_wanted = 0;
|
|
size_t write_length = 0;
|
|
size_t bytes_remaining = 0;
|
|
size_t bytes_written = 0;
|
|
size_t bytes_read = 0;
|
|
char *data = NULL;
|
|
qahw_out_buffer_t out_buf;
|
|
bool exit = false;
|
|
|
|
if (is_offload) {
|
|
fprintf(log_file, "Set callback for offload stream\n");
|
|
qahw_out_set_callback(out_handle, async_callback, NULL);
|
|
}
|
|
|
|
rc = qahw_out_set_volume(out_handle, vol_level, vol_level);
|
|
if (rc < 0)
|
|
fprintf(log_file, "unable to set volume");
|
|
|
|
bytes_wanted = qahw_out_get_buffer_size(out_handle);
|
|
data = (char *) malloc (bytes_wanted);
|
|
if (data == NULL) {
|
|
fprintf(log_file, "calloc failed!!\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
while (!exit) {
|
|
if (!bytes_remaining) {
|
|
bytes_read = fread(data, 1, bytes_wanted, in_file);
|
|
fprintf(log_file, "fread from file %ld\n", bytes_read);
|
|
if (bytes_read <= 0) {
|
|
if (feof(in_file)) {
|
|
fprintf(log_file, "End of file");
|
|
if (is_offload) {
|
|
pthread_mutex_lock(&drain_lock);
|
|
if (is_offload) {
|
|
qahw_out_drain(out_handle, QAHW_DRAIN_ALL);
|
|
pthread_cond_wait(&drain_cond, &drain_lock);
|
|
fprintf(log_file, "Out of compress drain\n");
|
|
}
|
|
pthread_mutex_unlock(&drain_lock);
|
|
}
|
|
} else {
|
|
fprintf(log_file, "Error in fread --%d\n", ferror(in_file));
|
|
fprintf(stderr, "Error in fread --%d\n", ferror(in_file));
|
|
}
|
|
exit = true;
|
|
continue;
|
|
}
|
|
bytes_remaining = write_length = bytes_read;
|
|
}
|
|
|
|
offset = write_length - bytes_remaining;
|
|
fprintf(log_file, "bytes_remaining %ld, offset %d, write length %ld\n",
|
|
bytes_remaining, offset, write_length);
|
|
bytes_written = write_to_hal(out_handle, data+offset, bytes_remaining);
|
|
bytes_remaining -= bytes_written;
|
|
fprintf(log_file, "bytes_written %ld, bytes_remaining %ld\n",
|
|
bytes_written, bytes_remaining);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool is_valid_aac_format_type(aac_format_type_t format_type)
|
|
{
|
|
bool valid_format_type = false;
|
|
|
|
switch (format_type) {
|
|
case AAC_LC:
|
|
case AAC_HE_V1:
|
|
case AAC_HE_V2:
|
|
valid_format_type = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return valid_format_type;
|
|
}
|
|
|
|
/*
|
|
* Obtain aac format (refer audio.h) for format type entered.
|
|
*/
|
|
|
|
audio_format_t get_aac_format(int filetype, aac_format_type_t format_type)
|
|
{
|
|
audio_format_t aac_format = AUDIO_FORMAT_AAC_ADTS_LC; /* default aac frmt*/
|
|
|
|
if (filetype == FILE_AAC_ADTS) {
|
|
switch (format_type) {
|
|
case AAC_LC:
|
|
aac_format = AUDIO_FORMAT_AAC_ADTS_LC;
|
|
break;
|
|
case AAC_HE_V1:
|
|
aac_format = AUDIO_FORMAT_AAC_ADTS_HE_V1;
|
|
break;
|
|
case AAC_HE_V2:
|
|
aac_format = AUDIO_FORMAT_AAC_ADTS_HE_V2;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else if (filetype == FILE_AAC) {
|
|
switch (format_type) {
|
|
case AAC_LC:
|
|
aac_format = AUDIO_FORMAT_AAC_LC;
|
|
break;
|
|
case AAC_HE_V1:
|
|
aac_format = AUDIO_FORMAT_AAC_HE_V1;
|
|
break;
|
|
case AAC_HE_V2:
|
|
aac_format = AUDIO_FORMAT_AAC_HE_V2;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
fprintf(log_file, "Invalid filetype provided %d\n", filetype);
|
|
fprintf(stderr, "Invalid filetype provided %d\n", filetype);
|
|
}
|
|
|
|
fprintf(log_file, "aac format %d\n", aac_format);
|
|
return aac_format;
|
|
}
|
|
|
|
void usage() {
|
|
printf(" \n Command \n");
|
|
printf(" \n hal_play_test <file path> - path of file to be played\n");
|
|
printf(" \n Options\n");
|
|
printf(" -r --sample-rate <sampling rate> - Required for Non-WAV streams\n");
|
|
printf(" For AAC-HE pls specify half the sample rate\n\n");
|
|
printf(" -c --channel count <channels> - Required for Non-WAV streams\n\n");
|
|
printf(" -v --volume <float volume level> - Volume level float value between 0.0 - 1.0.\n");
|
|
printf(" -d --device <decimal value> - see system/media/audio/include/system/audio.h for device values\n");
|
|
printf(" Optional Argument and Default value is 2, i.e Speaker\n\n");
|
|
printf(" -t --file-type <file type> - 1:WAV 2:MP3 3:AAC 4:AAC_ADTS\n");
|
|
printf(" Required for non WAV formats\n\n");
|
|
printf(" -a --aac-type <aac type> - Required for AAC streams\n");
|
|
printf(" 1: LC 2: HE_V1 3: HE_V2\n\n");
|
|
printf(" -l --log-file <FILEPATH> - File path for debug msg, to print\n");
|
|
printf(" on console use stdout or 1 \n\n");
|
|
printf(" \n Examples \n");
|
|
printf(" hal_play_test /etc/Anukoledenadu.wav -> plays Wav stream with default params\n\n");
|
|
printf(" hal_play_test /etc/MateRani.mp3 -t 2 -d 2 -v 0.01 -r 44100 -c 2 \n");
|
|
printf(" -> plays MP3 stream(-t = 2) on speaker device(-d = 2)\n");
|
|
printf(" -> 2 channels and 44100 sample rate\n\n");
|
|
printf(" hal_play_test /etc/AACLC-71-48000Hz-384000bps.aac -t 4 -d 2 -v 0.05 -r 48000 -c 2 -a 1 \n");
|
|
printf(" -> plays AAC-ADTS stream(-t = 4) on speaker device(-d = 2)\n");
|
|
printf(" -> AAC format type is LC(-a = 1)\n");
|
|
printf(" -> 2 channels and 48000 sample rate\n\n");
|
|
printf(" hal_play_test /etc/AACHE-adts-stereo-32000KHz-128000Kbps.aac -t 4 -d 2 -v 0.05 -r 16000 -c 2 -a 3 \n");
|
|
printf(" -> plays AAC-ADTS stream(-t = 4) on speaker device(-d = 2)\n");
|
|
printf(" -> AAC format type is HE V2(-a = 3)\n");
|
|
printf(" -> 2 channels and 16000 sample rate\n");
|
|
printf(" -> note that the sample rate is half the actual sample rate\n\n");
|
|
}
|
|
|
|
int main(int argc, char* argv[]) {
|
|
|
|
FILE *file_stream = NULL;
|
|
char header[44] = {0};
|
|
char* filename = nullptr;
|
|
qahw_module_handle_t *qahw_mod_handle;
|
|
const char *mod_name = "audio.primary";
|
|
qahw_stream_handle_t* out_handle = nullptr;
|
|
int rc = 0;
|
|
|
|
/*
|
|
* Default values
|
|
*/
|
|
int filetype = FILE_WAV;
|
|
int sample_rate = 44100;
|
|
int channels = 2;
|
|
const int audio_device_base = 0x2;/* spkr device*/
|
|
aac_format_type_t format_type = AAC_LC;
|
|
log_file = stdout;
|
|
audio_devices_t output_device = AUDIO_DEVICE_OUT_SPEAKER;
|
|
|
|
struct option long_options[] = {
|
|
/* These options set a flag. */
|
|
{"device", required_argument, 0, 'd'},
|
|
{"sample-rate", required_argument, 0, 'r'},
|
|
{"channels", required_argument, 0, 'c'},
|
|
{"volume", required_argument, 0, 'v'},
|
|
{"log-file", required_argument, 0, 'l'},
|
|
{"file-type", required_argument, 0, 't'},
|
|
{"aac-type", required_argument, 0, 'a'},
|
|
{"help", no_argument, 0, 'h'},
|
|
{0, 0, 0, 0}
|
|
};
|
|
|
|
int opt = 0;
|
|
int option_index = 0;
|
|
while ((opt = getopt_long(argc,
|
|
argv,
|
|
"-r:c:d:v:l::t:a:h",
|
|
long_options,
|
|
&option_index)) != -1) {
|
|
switch (opt) {
|
|
case 'r':
|
|
sample_rate = atoi(optarg);
|
|
break;
|
|
case 'c':;
|
|
channels = atoi(optarg);
|
|
break;
|
|
case 'd':
|
|
output_device = atoi(optarg);
|
|
break;
|
|
case 'v':
|
|
vol_level = atof(optarg);
|
|
break;
|
|
case 'l':
|
|
/*
|
|
* Fix Me: unable to log to a given file.
|
|
*/
|
|
log_filename = optarg;
|
|
if((log_file = fopen(log_filename,"wb"))== NULL) {
|
|
fprintf(stderr, "Cannot open log file %s\n", log_filename);
|
|
/*
|
|
* continue to log to std out.
|
|
*/
|
|
log_file = stdout;
|
|
}
|
|
|
|
break;
|
|
case 't':
|
|
filetype = atoi(optarg);
|
|
break;
|
|
case 'a':
|
|
format_type = atoi(optarg);
|
|
break;
|
|
case 'h':
|
|
usage();
|
|
return 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
filename = argv[1];
|
|
if((file_stream = fopen(filename, "r"))== NULL) {
|
|
fprintf(stderr, "Cannot Open Audio File %s\n", filename);
|
|
goto EXIT;
|
|
}
|
|
|
|
/*
|
|
* Set to a high number so it doesn't interfere with existing stream handles
|
|
*/
|
|
|
|
audio_io_handle_t handle = 0x999;
|
|
audio_output_flags_t flags = AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD;
|
|
|
|
fprintf(stdout, "Playing:%s\n", filename);
|
|
fprintf(stdout, "File Type:%d\n", filetype);
|
|
fprintf(stdout, "Sample Rate:%d\n", sample_rate);
|
|
fprintf(stdout, "Channels:%d\n", channels);
|
|
fprintf(stdout, "Log file:%s\n", log_filename);
|
|
fprintf(stdout, "Volume level:%f\n", vol_level);
|
|
fprintf(stdout, "Output Device:%d\n", output_device);
|
|
fprintf(stdout, "Format Type:%d\n", format_type);
|
|
|
|
fprintf(stdout, "Starting audio hal tests.\n");
|
|
|
|
qahw_mod_handle = qahw_load_module(mod_name);
|
|
|
|
audio_config_t config;
|
|
memset(&config, 0, sizeof(audio_config_t));
|
|
|
|
switch (filetype) {
|
|
case FILE_WAV:
|
|
/*
|
|
* Read the wave header
|
|
*/
|
|
rc = fread (header, 44 , 1, file_stream);
|
|
if (rc != 1) {
|
|
fprintf(stdout, "Error .Fread failed\n");
|
|
exit(0);
|
|
}
|
|
if (strncmp (header, "RIFF", 4) && strncmp (header+8, "WAVE", 4)) {
|
|
fprintf(stdout, "Not a wave format\n");
|
|
exit (1);
|
|
}
|
|
memcpy (&channels, &header[22], 2);
|
|
memcpy (&sample_rate, &header[24], 4);
|
|
config.channel_mask = audio_channel_out_mask_from_count(channels);
|
|
config.offload_info.channel_mask = config.channel_mask;
|
|
config.offload_info.sample_rate = sample_rate;
|
|
config.offload_info.format = AUDIO_FORMAT_PCM_16_BIT;
|
|
break;
|
|
|
|
case FILE_MP3:
|
|
config.channel_mask = audio_channel_out_mask_from_count(channels);
|
|
config.offload_info.channel_mask = config.channel_mask;
|
|
config.sample_rate = sample_rate;
|
|
config.offload_info.sample_rate = sample_rate;
|
|
config.offload_info.format = AUDIO_FORMAT_MP3;
|
|
flags |= AUDIO_OUTPUT_FLAG_NON_BLOCKING;
|
|
break;
|
|
|
|
case FILE_AAC:
|
|
case FILE_AAC_ADTS:
|
|
config.channel_mask = audio_channel_out_mask_from_count(channels);
|
|
config.offload_info.channel_mask = config.channel_mask;
|
|
config.sample_rate = sample_rate;
|
|
config.offload_info.sample_rate = sample_rate;
|
|
if (!is_valid_aac_format_type(format_type)) {
|
|
fprintf(log_file, "Invalid format type for AAC %d\n", format_type);
|
|
goto EXIT;
|
|
}
|
|
config.offload_info.format = get_aac_format(filetype, format_type);
|
|
flags |= AUDIO_OUTPUT_FLAG_NON_BLOCKING;
|
|
break;
|
|
|
|
|
|
default:
|
|
fprintf(stderr, "Does not support given filetype\n");
|
|
usage();
|
|
return 0;
|
|
}
|
|
config.offload_info.version = AUDIO_OFFLOAD_INFO_VERSION_CURRENT;
|
|
config.offload_info.size = sizeof(audio_offload_info_t);
|
|
|
|
fprintf(log_file, "Now playing to output_device=%d sample_rate=%d \n"
|
|
, output_device, config.offload_info.sample_rate);
|
|
const char* stream_name = "output_stream";
|
|
|
|
fprintf(log_file, "calling open_out_put_stream:\n");
|
|
rc = qahw_open_output_stream(qahw_mod_handle,
|
|
handle,
|
|
output_device,
|
|
flags,
|
|
&config,
|
|
&out_handle,
|
|
stream_name);
|
|
fprintf(log_file, "open output stream is sucess:%d out_handhle %p\n"
|
|
, rc, out_handle);
|
|
if (rc) {
|
|
fprintf(stdout, "could not open output stream %d \n", rc);
|
|
goto EXIT;
|
|
}
|
|
|
|
play_file(out_handle,
|
|
file_stream,
|
|
(flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD));
|
|
|
|
EXIT:
|
|
|
|
if (out_handle != nullptr) {
|
|
rc = qahw_out_standby(out_handle);
|
|
if (rc) {
|
|
fprintf(stdout, "out standby failed %d \n", rc);
|
|
}
|
|
|
|
rc = qahw_close_output_stream(out_handle);
|
|
if (rc) {
|
|
fprintf(stdout, "could not close output stream %d \n", rc);
|
|
}
|
|
|
|
rc = qahw_unload_module(qahw_mod_handle);
|
|
if (rc) {
|
|
fprintf(stdout, "could not unload hal %d \n", rc);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if ((log_file != stdout) && (log_file != nullptr))
|
|
fclose(log_file);
|
|
|
|
if (file_stream != nullptr)
|
|
fclose(file_stream);
|
|
|
|
fprintf(stdout, "\nBYE BYE\n");
|
|
return 0;
|
|
}
|