tinyplay.c 8.28 KB
Newer Older
Simon Wilson's avatar
Simon Wilson committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
/* 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>
Simon Wilson's avatar
Simon Wilson committed
33 34
#include <string.h>
#include <signal.h>
Simon Wilson's avatar
Simon Wilson committed
35 36 37 38 39 40

#define ID_RIFF 0x46464952
#define ID_WAVE 0x45564157
#define ID_FMT  0x20746d66
#define ID_DATA 0x61746164

Simon Wilson's avatar
Simon Wilson committed
41
struct riff_wave_header {
Simon Wilson's avatar
Simon Wilson committed
42 43
    uint32_t riff_id;
    uint32_t riff_sz;
Simon Wilson's avatar
Simon Wilson committed
44 45 46 47 48 49 50 51 52
    uint32_t wave_id;
};

struct chunk_header {
    uint32_t id;
    uint32_t sz;
};

struct chunk_fmt {
Simon Wilson's avatar
Simon Wilson committed
53 54 55 56 57 58 59 60
    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;
};

Simon Wilson's avatar
Simon Wilson committed
61 62
static int close = 0;

Simon Wilson's avatar
Simon Wilson committed
63 64 65
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);
Simon Wilson's avatar
Simon Wilson committed
66

Simon Wilson's avatar
Simon Wilson committed
67 68 69 70 71 72 73
void stream_close(int sig)
{
    /* allow the stream to be closed gracefully */
    signal(sig, SIG_IGN);
    close = 1;
}

Simon Wilson's avatar
Simon Wilson committed
74 75 76
int main(int argc, char **argv)
{
    FILE *file;
Simon Wilson's avatar
Simon Wilson committed
77 78 79
    struct riff_wave_header riff_wave_header;
    struct chunk_header chunk_header;
    struct chunk_fmt chunk_fmt;
Simon Wilson's avatar
Simon Wilson committed
80
    unsigned int device = 0;
Simon Wilson's avatar
Simon Wilson committed
81 82 83
    unsigned int card = 0;
    unsigned int period_size = 1024;
    unsigned int period_count = 4;
Simon Wilson's avatar
Simon Wilson committed
84 85
    char *filename;
    int more_chunks = 1;
Simon Wilson's avatar
Simon Wilson committed
86

Simon Wilson's avatar
Simon Wilson committed
87
    if (argc < 2) {
Simon Wilson's avatar
Simon Wilson committed
88 89
        fprintf(stderr, "Usage: %s file.wav [-D card] [-d device] [-p period_size]"
                " [-n n_periods] \n", argv[0]);
Simon Wilson's avatar
Simon Wilson committed
90 91 92
        return 1;
    }

Simon Wilson's avatar
Simon Wilson committed
93 94
    filename = argv[1];
    file = fopen(filename, "rb");
Simon Wilson's avatar
Simon Wilson committed
95
    if (!file) {
Simon Wilson's avatar
Simon Wilson committed
96 97 98 99 100 101 102 103 104
        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);
Simon Wilson's avatar
Simon Wilson committed
105 106 107
        return 1;
    }

Simon Wilson's avatar
Simon Wilson committed
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
    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);

Simon Wilson's avatar
Simon Wilson committed
128 129
    /* parse command line arguments */
    argv += 2;
Simon Wilson's avatar
Simon Wilson committed
130 131 132
    while (*argv) {
        if (strcmp(*argv, "-d") == 0) {
            argv++;
Simon Wilson's avatar
Simon Wilson committed
133 134
            if (*argv)
                device = atoi(*argv);
Simon Wilson's avatar
Simon Wilson committed
135
        }
Simon Wilson's avatar
Simon Wilson committed
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
        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++;
Simon Wilson's avatar
Simon Wilson committed
153 154
    }

Simon Wilson's avatar
Simon Wilson committed
155 156
    play_sample(file, card, device, chunk_fmt.num_channels, chunk_fmt.sample_rate,
                chunk_fmt.bits_per_sample, period_size, period_count);
Simon Wilson's avatar
Simon Wilson committed
157 158 159 160 161 162

    fclose(file);

    return 0;
}

Simon Wilson's avatar
Simon Wilson committed
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
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;
}

Simon Wilson's avatar
Simon Wilson committed
211 212 213
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)
Simon Wilson's avatar
Simon Wilson committed
214 215 216 217 218 219 220
{
    struct pcm_config config;
    struct pcm *pcm;
    char *buffer;
    int size;
    int num_read;

221
    memset(&config, 0, sizeof(config));
Simon Wilson's avatar
Simon Wilson committed
222 223
    config.channels = channels;
    config.rate = rate;
Simon Wilson's avatar
Simon Wilson committed
224 225
    config.period_size = period_size;
    config.period_count = period_count;
Simon Wilson's avatar
Simon Wilson committed
226 227 228 229
    if (bits == 32)
        config.format = PCM_FORMAT_S32_LE;
    else if (bits == 16)
        config.format = PCM_FORMAT_S16_LE;
Simon Wilson's avatar
Simon Wilson committed
230 231 232
    config.start_threshold = 0;
    config.stop_threshold = 0;
    config.silence_threshold = 0;
Simon Wilson's avatar
Simon Wilson committed
233

Simon Wilson's avatar
Simon Wilson committed
234 235 236 237
    if (!sample_is_playable(card, device, channels, rate, bits, period_size, period_count)) {
        return;
    }

Simon Wilson's avatar
Simon Wilson committed
238
    pcm = pcm_open(card, device, PCM_OUT, &config);
Simon Wilson's avatar
Simon Wilson committed
239
    if (!pcm || !pcm_is_ready(pcm)) {
Simon Wilson's avatar
Simon Wilson committed
240 241
        fprintf(stderr, "Unable to open PCM device %u (%s)\n",
                device, pcm_get_error(pcm));
Simon Wilson's avatar
Simon Wilson committed
242 243 244
        return;
    }

Simon Wilson's avatar
Simon Wilson committed
245
    size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
Simon Wilson's avatar
Simon Wilson committed
246 247 248 249 250 251 252 253 254 255
    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);

Simon Wilson's avatar
Simon Wilson committed
256 257 258
    /* catch ctrl-c to shutdown cleanly */
    signal(SIGINT, stream_close);

Simon Wilson's avatar
Simon Wilson committed
259 260 261 262 263 264 265 266
    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;
            }
        }
Simon Wilson's avatar
Simon Wilson committed
267
    } while (!close && num_read > 0);
Simon Wilson's avatar
Simon Wilson committed
268 269 270 271 272

    free(buffer);
    pcm_close(pcm);
}