|  | /* tinyplay.c | 
|  | ** | 
|  | ** Copyright 2011, The Android Open Source Project | 
|  | ** | 
|  | ** Redistribution and use in source and binary forms, with or without | 
|  | ** modification, are permitted provided that the following conditions are met: | 
|  | **     * Redistributions of source code must retain the above copyright | 
|  | **       notice, this list of conditions and the following disclaimer. | 
|  | **     * Redistributions in binary form must reproduce the above copyright | 
|  | **       notice, this list of conditions and the following disclaimer in the | 
|  | **       documentation and/or other materials provided with the distribution. | 
|  | **     * Neither the name of The Android Open Source Project nor the names of | 
|  | **       its contributors may be used to endorse or promote products derived | 
|  | **       from this software without specific prior written permission. | 
|  | ** | 
|  | ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND | 
|  | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
|  | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | 
|  | ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE | 
|  | ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | 
|  | ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | 
|  | ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | 
|  | ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | 
|  | ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | 
|  | ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH | 
|  | ** DAMAGE. | 
|  | */ | 
|  |  | 
|  | #include <tinyalsa/asoundlib.h> | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <stdint.h> | 
|  | #include <string.h> | 
|  | #include <signal.h> | 
|  |  | 
|  | #define ID_RIFF 0x46464952 | 
|  | #define ID_WAVE 0x45564157 | 
|  | #define ID_FMT  0x20746d66 | 
|  | #define ID_DATA 0x61746164 | 
|  |  | 
|  | struct riff_wave_header { | 
|  | uint32_t riff_id; | 
|  | uint32_t riff_sz; | 
|  | uint32_t wave_id; | 
|  | }; | 
|  |  | 
|  | struct chunk_header { | 
|  | uint32_t id; | 
|  | uint32_t sz; | 
|  | }; | 
|  |  | 
|  | struct chunk_fmt { | 
|  | uint16_t audio_format; | 
|  | uint16_t num_channels; | 
|  | uint32_t sample_rate; | 
|  | uint32_t byte_rate; | 
|  | uint16_t block_align; | 
|  | uint16_t bits_per_sample; | 
|  | }; | 
|  |  | 
|  | static int close = 0; | 
|  |  | 
|  | void play_sample(FILE *file, unsigned int card, unsigned int device, unsigned int channels, | 
|  | unsigned int rate, unsigned int bits, unsigned int period_size, | 
|  | unsigned int period_count); | 
|  |  | 
|  | void stream_close(int sig) | 
|  | { | 
|  | /* allow the stream to be closed gracefully */ | 
|  | signal(sig, SIG_IGN); | 
|  | close = 1; | 
|  | } | 
|  |  | 
|  | int main(int argc, char **argv) | 
|  | { | 
|  | FILE *file; | 
|  | struct riff_wave_header riff_wave_header; | 
|  | struct chunk_header chunk_header; | 
|  | struct chunk_fmt chunk_fmt; | 
|  | unsigned int device = 0; | 
|  | unsigned int card = 0; | 
|  | unsigned int period_size = 1024; | 
|  | unsigned int period_count = 4; | 
|  | char *filename; | 
|  | int more_chunks = 1; | 
|  |  | 
|  | if (argc < 2) { | 
|  | fprintf(stderr, "Usage: %s file.wav [-D card] [-d device] [-p period_size]" | 
|  | " [-n n_periods] \n", argv[0]); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | filename = argv[1]; | 
|  | file = fopen(filename, "rb"); | 
|  | if (!file) { | 
|  | fprintf(stderr, "Unable to open file '%s'\n", filename); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | fread(&riff_wave_header, sizeof(riff_wave_header), 1, file); | 
|  | if ((riff_wave_header.riff_id != ID_RIFF) || | 
|  | (riff_wave_header.wave_id != ID_WAVE)) { | 
|  | fprintf(stderr, "Error: '%s' is not a riff/wave file\n", filename); | 
|  | fclose(file); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | do { | 
|  | fread(&chunk_header, sizeof(chunk_header), 1, file); | 
|  |  | 
|  | switch (chunk_header.id) { | 
|  | case ID_FMT: | 
|  | fread(&chunk_fmt, sizeof(chunk_fmt), 1, file); | 
|  | /* If the format header is larger, skip the rest */ | 
|  | if (chunk_header.sz > sizeof(chunk_fmt)) | 
|  | fseek(file, chunk_header.sz - sizeof(chunk_fmt), SEEK_CUR); | 
|  | break; | 
|  | case ID_DATA: | 
|  | /* Stop looking for chunks */ | 
|  | more_chunks = 0; | 
|  | break; | 
|  | default: | 
|  | /* Unknown chunk, skip bytes */ | 
|  | fseek(file, chunk_header.sz, SEEK_CUR); | 
|  | } | 
|  | } while (more_chunks); | 
|  |  | 
|  | /* parse command line arguments */ | 
|  | argv += 2; | 
|  | while (*argv) { | 
|  | if (strcmp(*argv, "-d") == 0) { | 
|  | argv++; | 
|  | if (*argv) | 
|  | device = atoi(*argv); | 
|  | } | 
|  | if (strcmp(*argv, "-p") == 0) { | 
|  | argv++; | 
|  | if (*argv) | 
|  | period_size = atoi(*argv); | 
|  | } | 
|  | if (strcmp(*argv, "-n") == 0) { | 
|  | argv++; | 
|  | if (*argv) | 
|  | period_count = atoi(*argv); | 
|  | } | 
|  | if (strcmp(*argv, "-D") == 0) { | 
|  | argv++; | 
|  | if (*argv) | 
|  | card = atoi(*argv); | 
|  | } | 
|  | if (*argv) | 
|  | argv++; | 
|  | } | 
|  |  | 
|  | play_sample(file, card, device, chunk_fmt.num_channels, chunk_fmt.sample_rate, | 
|  | chunk_fmt.bits_per_sample, period_size, period_count); | 
|  |  | 
|  | fclose(file); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int check_param(struct pcm_params *params, unsigned int param, unsigned int value, | 
|  | char *param_name, char *param_unit) | 
|  | { | 
|  | unsigned int min; | 
|  | unsigned int max; | 
|  | int is_within_bounds = 1; | 
|  |  | 
|  | min = pcm_params_get_min(params, param); | 
|  | if (value < min) { | 
|  | fprintf(stderr, "%s is %u%s, device only supports >= %u%s\n", param_name, value, | 
|  | param_unit, min, param_unit); | 
|  | is_within_bounds = 0; | 
|  | } | 
|  |  | 
|  | max = pcm_params_get_max(params, param); | 
|  | if (value > max) { | 
|  | fprintf(stderr, "%s is %u%s, device only supports <= %u%s\n", param_name, value, | 
|  | param_unit, max, param_unit); | 
|  | is_within_bounds = 0; | 
|  | } | 
|  |  | 
|  | return is_within_bounds; | 
|  | } | 
|  |  | 
|  | int sample_is_playable(unsigned int card, unsigned int device, unsigned int channels, | 
|  | unsigned int rate, unsigned int bits, unsigned int period_size, | 
|  | unsigned int period_count) | 
|  | { | 
|  | struct pcm_params *params; | 
|  | int can_play; | 
|  |  | 
|  | params = pcm_params_get(card, device, PCM_OUT); | 
|  | if (params == NULL) { | 
|  | fprintf(stderr, "Unable to open PCM device %u.\n", device); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | can_play = check_param(params, PCM_PARAM_RATE, rate, "Sample rate", "Hz"); | 
|  | can_play &= check_param(params, PCM_PARAM_CHANNELS, channels, "Sample", " channels"); | 
|  | can_play &= check_param(params, PCM_PARAM_SAMPLE_BITS, bits, "Bitrate", " bits"); | 
|  | can_play &= check_param(params, PCM_PARAM_PERIOD_SIZE, period_size, "Period size", "Hz"); | 
|  | can_play &= check_param(params, PCM_PARAM_PERIODS, period_count, "Period count", "Hz"); | 
|  |  | 
|  | pcm_params_free(params); | 
|  |  | 
|  | return can_play; | 
|  | } | 
|  |  | 
|  | void play_sample(FILE *file, unsigned int card, unsigned int device, unsigned int channels, | 
|  | unsigned int rate, unsigned int bits, unsigned int period_size, | 
|  | unsigned int period_count) | 
|  | { | 
|  | struct pcm_config config; | 
|  | struct pcm *pcm; | 
|  | char *buffer; | 
|  | int size; | 
|  | int num_read; | 
|  |  | 
|  | memset(&config, 0, sizeof(config)); | 
|  | config.channels = channels; | 
|  | config.rate = rate; | 
|  | config.period_size = period_size; | 
|  | config.period_count = period_count; | 
|  | if (bits == 32) | 
|  | config.format = PCM_FORMAT_S32_LE; | 
|  | else if (bits == 16) | 
|  | config.format = PCM_FORMAT_S16_LE; | 
|  | config.start_threshold = 0; | 
|  | config.stop_threshold = 0; | 
|  | config.silence_threshold = 0; | 
|  |  | 
|  | if (!sample_is_playable(card, device, channels, rate, bits, period_size, period_count)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | pcm = pcm_open(card, device, PCM_OUT, &config); | 
|  | if (!pcm || !pcm_is_ready(pcm)) { | 
|  | fprintf(stderr, "Unable to open PCM device %u (%s)\n", | 
|  | device, pcm_get_error(pcm)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm)); | 
|  | buffer = malloc(size); | 
|  | if (!buffer) { | 
|  | fprintf(stderr, "Unable to allocate %d bytes\n", size); | 
|  | free(buffer); | 
|  | pcm_close(pcm); | 
|  | return; | 
|  | } | 
|  |  | 
|  | printf("Playing sample: %u ch, %u hz, %u bit\n", channels, rate, bits); | 
|  |  | 
|  | /* catch ctrl-c to shutdown cleanly */ | 
|  | signal(SIGINT, stream_close); | 
|  |  | 
|  | do { | 
|  | num_read = fread(buffer, 1, size, file); | 
|  | if (num_read > 0) { | 
|  | if (pcm_write(pcm, buffer, num_read)) { | 
|  | fprintf(stderr, "Error playing sample\n"); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } while (!close && num_read > 0); | 
|  |  | 
|  | free(buffer); | 
|  | pcm_close(pcm); | 
|  | } | 
|  |  |