/* * Copyright (C) 2014 Intel Corporation * * 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 "if-main.h" #include "../hal-utils.h" #include "pthread.h" #include "unistd.h" #include audio_hw_device_t *if_audio = NULL; static struct audio_stream_out *stream_out = NULL; static size_t buffer_size = 0; static pthread_t play_thread = 0; static pthread_mutex_t outstream_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER; enum state { STATE_STOPPED, STATE_STOPPING, STATE_PLAYING, STATE_SUSPENDED, STATE_MAX }; SINTMAP(audio_channel_mask_t, -1, "(AUDIO_CHANNEL_INVALID)") DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), DELEMENT(AUDIO_CHANNEL_OUT_FRONT_RIGHT), DELEMENT(AUDIO_CHANNEL_OUT_FRONT_CENTER), DELEMENT(AUDIO_CHANNEL_OUT_LOW_FREQUENCY), DELEMENT(AUDIO_CHANNEL_OUT_BACK_LEFT), DELEMENT(AUDIO_CHANNEL_OUT_BACK_RIGHT), DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER), DELEMENT(AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER), DELEMENT(AUDIO_CHANNEL_OUT_BACK_CENTER), DELEMENT(AUDIO_CHANNEL_OUT_SIDE_LEFT), DELEMENT(AUDIO_CHANNEL_OUT_SIDE_RIGHT), DELEMENT(AUDIO_CHANNEL_OUT_TOP_CENTER), DELEMENT(AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT), DELEMENT(AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER), DELEMENT(AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT), DELEMENT(AUDIO_CHANNEL_OUT_TOP_BACK_LEFT), DELEMENT(AUDIO_CHANNEL_OUT_TOP_BACK_CENTER), DELEMENT(AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT), DELEMENT(AUDIO_CHANNEL_OUT_MONO), DELEMENT(AUDIO_CHANNEL_OUT_STEREO), DELEMENT(AUDIO_CHANNEL_OUT_QUAD), DELEMENT(AUDIO_CHANNEL_OUT_SURROUND), DELEMENT(AUDIO_CHANNEL_OUT_5POINT1), DELEMENT(AUDIO_CHANNEL_OUT_7POINT1), DELEMENT(AUDIO_CHANNEL_OUT_ALL), DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), ENDMAP SINTMAP(audio_format_t, -1, "(AUDIO_FORMAT_INVALID)") DELEMENT(AUDIO_FORMAT_DEFAULT), DELEMENT(AUDIO_FORMAT_PCM), DELEMENT(AUDIO_FORMAT_MP3), DELEMENT(AUDIO_FORMAT_AMR_NB), DELEMENT(AUDIO_FORMAT_AMR_WB), DELEMENT(AUDIO_FORMAT_AAC), DELEMENT(AUDIO_FORMAT_HE_AAC_V1), DELEMENT(AUDIO_FORMAT_HE_AAC_V2), DELEMENT(AUDIO_FORMAT_VORBIS), DELEMENT(AUDIO_FORMAT_MAIN_MASK), DELEMENT(AUDIO_FORMAT_SUB_MASK), DELEMENT(AUDIO_FORMAT_PCM_16_BIT), DELEMENT(AUDIO_FORMAT_PCM_8_BIT), DELEMENT(AUDIO_FORMAT_PCM_32_BIT), DELEMENT(AUDIO_FORMAT_PCM_8_24_BIT), ENDMAP static int current_state = STATE_STOPPED; #define SAMPLERATE 44100 static short sample[SAMPLERATE]; static uint16_t sample_pos; static void init_p(int argc, const char **argv) { int err; const hw_module_t *module; audio_hw_device_t *device; err = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, AUDIO_HARDWARE_MODULE_ID_A2DP, &module); if (err) { haltest_error("hw_get_module_by_class returned %d\n", err); return; } err = audio_hw_device_open(module, &device); if (err) { haltest_error("audio_hw_device_open returned %d\n", err); return; } if_audio = device; } static int feed_from_file(short *buffer, void *data) { FILE *in = data; return fread(buffer, buffer_size, 1, in); } static int feed_from_generator(short *buffer, void *data) { size_t i = 0; float volume = 0.5; float *freq = data; float f = 1; if (freq) f = *freq; /* buffer_size is in bytes but we are using buffer of shorts (2 bytes)*/ for (i = 0; i < buffer_size / sizeof(*buffer) - 1;) { if (sample_pos >= SAMPLERATE) sample_pos = sample_pos % SAMPLERATE; /* Use the same sample for both channels */ buffer[i++] = sample[sample_pos] * volume; buffer[i++] = sample[sample_pos] * volume; sample_pos += f; } return buffer_size; } static void prepare_sample(void) { int x; double s; haltest_info("Preparing audio sample...\n"); for (x = 0; x < SAMPLERATE; x++) { /* prepare sinusoidal 1Hz sample */ s = (2.0 * 3.14159) * ((double)x / SAMPLERATE); s = sin(s); /* remap <-1, 1> to signed 16bit PCM range */ sample[x] = s * 32767; } sample_pos = 0; } static void *playback_thread(void *data) { int (*filbuff_cb) (short*, void*); short buffer[buffer_size / sizeof(short)]; size_t len = 0; ssize_t w_len = 0; FILE *in = data; void *cb_data = NULL; float freq = 440.0; /* Use file or fall back to generator */ if (in) { filbuff_cb = feed_from_file; cb_data = in; } else { prepare_sample(); filbuff_cb = feed_from_generator; cb_data = &freq; } pthread_mutex_lock(&state_mutex); current_state = STATE_PLAYING; pthread_mutex_unlock(&state_mutex); do { pthread_mutex_lock(&state_mutex); if (current_state == STATE_STOPPING) { pthread_mutex_unlock(&state_mutex); break; } else if (current_state == STATE_SUSPENDED) { pthread_mutex_unlock(&state_mutex); usleep(500); continue; } pthread_mutex_unlock(&state_mutex); len = filbuff_cb(buffer, cb_data); pthread_mutex_lock(&outstream_mutex); if (!stream_out) { pthread_mutex_unlock(&outstream_mutex); break; } w_len = stream_out->write(stream_out, buffer, buffer_size); pthread_mutex_unlock(&outstream_mutex); } while (len && w_len > 0); if (in) fclose(in); pthread_mutex_lock(&state_mutex); current_state = STATE_STOPPED; pthread_mutex_unlock(&state_mutex); haltest_info("Done playing.\n"); return NULL; } static void play_p(int argc, const char **argv) { const char *fname = NULL; FILE *in = NULL; RETURN_IF_NULL(if_audio); RETURN_IF_NULL(stream_out); if (argc < 3) { haltest_error("Invalid audio file path.\n"); haltest_info("Using sound generator.\n"); } else { fname = argv[2]; in = fopen(fname, "r"); if (in == NULL) { haltest_error("Cannot open file: %s\n", fname); return; } haltest_info("Playing file: %s\n", fname); } if (buffer_size == 0) { haltest_error("Invalid buffer size. Was stream_out opened?\n"); goto fail; } pthread_mutex_lock(&state_mutex); if (current_state != STATE_STOPPED) { haltest_error("Already playing or stream suspended!\n"); pthread_mutex_unlock(&state_mutex); goto fail; } pthread_mutex_unlock(&state_mutex); if (pthread_create(&play_thread, NULL, playback_thread, in) != 0) { haltest_error("Cannot create playback thread!\n"); goto fail; } return; fail: if (in) fclose(in); } static void stop_p(int argc, const char **argv) { pthread_mutex_lock(&state_mutex); if (current_state == STATE_STOPPED || current_state == STATE_STOPPING) { pthread_mutex_unlock(&state_mutex); return; } current_state = STATE_STOPPING; pthread_mutex_unlock(&state_mutex); pthread_mutex_lock(&outstream_mutex); stream_out->common.standby(&stream_out->common); pthread_mutex_unlock(&outstream_mutex); } static void open_output_stream_p(int argc, const char **argv) { int err; RETURN_IF_NULL(if_audio); pthread_mutex_lock(&state_mutex); if (current_state == STATE_PLAYING) { haltest_error("Already playing!\n"); pthread_mutex_unlock(&state_mutex); return; } pthread_mutex_unlock(&state_mutex); err = if_audio->open_output_stream(if_audio, 0, AUDIO_DEVICE_OUT_ALL_A2DP, AUDIO_OUTPUT_FLAG_NONE, NULL, &stream_out); if (err < 0) { haltest_error("open output stream returned %d\n", err); return; } buffer_size = stream_out->common.get_buffer_size(&stream_out->common); if (buffer_size == 0) haltest_error("Invalid buffer size received!\n"); else haltest_info("Using buffer size: %zu\n", buffer_size); } static void close_output_stream_p(int argc, const char **argv) { RETURN_IF_NULL(if_audio); RETURN_IF_NULL(stream_out); stop_p(argc, argv); haltest_info("Waiting for playback thread...\n"); pthread_join(play_thread, NULL); if_audio->close_output_stream(if_audio, stream_out); stream_out = NULL; buffer_size = 0; } static void cleanup_p(int argc, const char **argv) { int err; RETURN_IF_NULL(if_audio); pthread_mutex_lock(&state_mutex); if (current_state != STATE_STOPPED) { pthread_mutex_unlock(&state_mutex); close_output_stream_p(0, NULL); } else { pthread_mutex_unlock(&state_mutex); } err = audio_hw_device_close(if_audio); if (err < 0) { haltest_error("audio_hw_device_close returned %d\n", err); return; } if_audio = NULL; } static void suspend_p(int argc, const char **argv) { RETURN_IF_NULL(if_audio); RETURN_IF_NULL(stream_out); pthread_mutex_lock(&state_mutex); if (current_state != STATE_PLAYING) { pthread_mutex_unlock(&state_mutex); return; } current_state = STATE_SUSPENDED; pthread_mutex_unlock(&state_mutex); pthread_mutex_lock(&outstream_mutex); stream_out->common.standby(&stream_out->common); pthread_mutex_unlock(&outstream_mutex); } static void resume_p(int argc, const char **argv) { RETURN_IF_NULL(if_audio); RETURN_IF_NULL(stream_out); pthread_mutex_lock(&state_mutex); if (current_state == STATE_SUSPENDED) current_state = STATE_PLAYING; pthread_mutex_unlock(&state_mutex); } static void get_latency_p(int argc, const char **argv) { RETURN_IF_NULL(if_audio); RETURN_IF_NULL(stream_out); haltest_info("Output audio stream latency: %d\n", stream_out->get_latency(stream_out)); } static void get_buffer_size_p(int argc, const char **argv) { RETURN_IF_NULL(if_audio); RETURN_IF_NULL(stream_out); haltest_info("Current output buffer size: %zu\n", stream_out->common.get_buffer_size(&stream_out->common)); } static void get_channels_p(int argc, const char **argv) { audio_channel_mask_t channels; RETURN_IF_NULL(if_audio); RETURN_IF_NULL(stream_out); channels = stream_out->common.get_channels(&stream_out->common); haltest_info("Channels: %s\n", audio_channel_mask_t2str(channels)); } static void get_format_p(int argc, const char **argv) { audio_format_t format; RETURN_IF_NULL(if_audio); RETURN_IF_NULL(stream_out); format = stream_out->common.get_format(&stream_out->common); haltest_info("Format: %s\n", audio_format_t2str(format)); } static void get_sample_rate_p(int argc, const char **argv) { RETURN_IF_NULL(if_audio); RETURN_IF_NULL(stream_out); haltest_info("Current sample rate: %d\n", stream_out->common.get_sample_rate(&stream_out->common)); } static void get_parameters_p(int argc, const char **argv) { const char *keystr; RETURN_IF_NULL(if_audio); RETURN_IF_NULL(stream_out); if (argc < 3) { haltest_info("No keys given.\n"); keystr = ""; } else { keystr = argv[2]; } haltest_info("Current parameters: %s\n", stream_out->common.get_parameters(&stream_out->common, keystr)); } static void set_parameters_p(int argc, const char **argv) { RETURN_IF_NULL(if_audio); RETURN_IF_NULL(stream_out); if (argc < 3) { haltest_error("No key=value; pairs given.\n"); return; } stream_out->common.set_parameters(&stream_out->common, argv[2]); } static void set_sample_rate_p(int argc, const char **argv) { RETURN_IF_NULL(if_audio); RETURN_IF_NULL(stream_out); if (argc < 3) return; stream_out->common.set_sample_rate(&stream_out->common, atoi(argv[2])); } static void init_check_p(int argc, const char **argv) { RETURN_IF_NULL(if_audio); haltest_info("Init check result: %d\n", if_audio->init_check(if_audio)); } static struct method methods[] = { STD_METHOD(init), STD_METHOD(cleanup), STD_METHOD(open_output_stream), STD_METHOD(close_output_stream), STD_METHODH(play, ""), STD_METHOD(stop), STD_METHOD(suspend), STD_METHOD(resume), STD_METHOD(get_latency), STD_METHOD(get_buffer_size), STD_METHOD(get_channels), STD_METHOD(get_format), STD_METHOD(get_sample_rate), STD_METHODH(get_parameters, ""), STD_METHODH(set_parameters, ""), STD_METHODH(set_sample_rate, ""), STD_METHOD(init_check), END_METHOD }; const struct interface audio_if = { .name = "audio", .methods = methods };