| /* |
| * Copyright 2016 Google 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. |
| */ |
| |
| #define _BSD_SOURCE |
| #include <arpa/inet.h> |
| #include <endian.h> |
| #include <fcntl.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/select.h> |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include "rcu-audio.h" |
| #include "remote_control_audio.pb.h" |
| |
| |
| typedef struct WAV_hdr |
| { |
| uint32_t chunk_id; |
| uint32_t chunk_size; |
| uint32_t format; |
| |
| uint32_t subchunk1_id; |
| uint32_t subchunk1_size; |
| 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; |
| |
| uint32_t subchunk2_id; |
| uint32_t subchunk2_size; |
| } WAV_hdr_t; |
| |
| |
| static int usage(const char *progname) |
| { |
| fprintf(stderr, "usage: %s [-f outfile]\n, where:", progname); |
| fprintf(stderr, "\t-f outfile: file to write audio to in WAV format.\n"); |
| exit(1); |
| } |
| |
| |
| int main(int argc, char **argv) |
| { |
| mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP; |
| int fd; |
| struct sockaddr_in sin; |
| const char *outfile = "/tmp/audio.wav"; |
| int outfd; |
| uint8_t buf[8192]; |
| WAV_hdr_t hdr; |
| ssize_t len, totlen=0; |
| int c; |
| struct timeval tv; |
| const char *model = "UNKNOWN"; |
| |
| memset(buf, 0, sizeof(buf)); |
| memset(&hdr, 0, sizeof(hdr)); |
| |
| while ((c = getopt(argc, argv, "f:")) != -1) { |
| switch (c) { |
| case 'f': |
| outfile = optarg; |
| break; |
| default: |
| usage(argv[0]); |
| break; |
| } |
| } |
| |
| if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { |
| perror("socket(AF_INET) RCU_AUDIO"); |
| exit(1); |
| } |
| memset(&sin, 0, sizeof(sin)); |
| sin.sin_family = AF_INET; |
| sin.sin_port = htons(RCU_AUDIO_PORT); |
| sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
| |
| if (bind(fd, (const struct sockaddr *) &sin, sizeof(sin)) < 0) { |
| perror("bind(AF_INET) RCU_AUDIO_PORT"); |
| exit(1); |
| } |
| |
| if ((outfd = open(outfile, O_RDWR|O_CREAT|O_TRUNC, mode)) < 0) { |
| fprintf(stderr, "Unable to open %s for writing.\n", outfile); |
| exit(1); |
| } |
| |
| if ((len = write(outfd, &hdr, sizeof(WAV_hdr_t))) != sizeof(WAV_hdr_t)) { |
| fprintf(stderr, "Incorrect size for WAV header: %zd != %zd\n", |
| len, sizeof(WAV_hdr_t)); |
| exit(1); |
| } |
| |
| tv.tv_sec = 0x7fffffff; |
| tv.tv_usec = 0; |
| |
| while (1) { |
| fd_set rfds; |
| |
| FD_ZERO(&rfds); |
| FD_SET(fd, &rfds); |
| if (select(fd + 1, &rfds, NULL, NULL, &tv) <= 0) { |
| /* No more data, close the output and exit. */ |
| break; |
| } |
| |
| len = read(fd, buf, sizeof(buf)); |
| if (len > 0) { |
| rcaudio::AudioSamples samples; |
| const char *data; |
| ssize_t data_len; |
| |
| if (!samples.ParseFromArray(buf, len)) { |
| if (pacing()) { |
| printf("failed to parse rcaudio::AudioSamples.\n"); |
| } |
| continue; |
| } |
| |
| if (samples.audio_format() != rcaudio::AudioSamples::PCM_16BIT_16KHZ) { |
| /* if we ever build a remote with a different format, we'll need |
| * to keep track of it here and adjust the WAV header to match. */ |
| if (pacing()) { |
| fprintf(stderr, "unknown audio format %d\n", samples.audio_format()); |
| } |
| continue; |
| } |
| |
| switch (samples.remote_type()) { |
| case rcaudio::AudioSamples::GFRM210: model = "GFRM210"; break; |
| case rcaudio::AudioSamples::GFRM100: model = "GFRM100"; break; |
| |
| case rcaudio::AudioSamples::UNDEFINED_REMOTE_TYPE: |
| default: |
| model = "UNKNOWN"; |
| break; |
| } |
| |
| data = samples.audio_samples().c_str(); |
| data_len = samples.audio_samples().size(); |
| totlen += data_len; |
| if (write(outfd, data, data_len) != data_len) { |
| fprintf(stderr, "short write!\n"); |
| exit(1); |
| } |
| } else if (len == 0) { |
| break; |
| } else if (len < 0) { |
| perror("read"); |
| exit(1); |
| } |
| tv.tv_sec = 2; |
| tv.tv_usec = 0; |
| } |
| |
| /* print the remote control type to stdout, demo script uses it. */ |
| puts(model); |
| |
| lseek(outfd, 0, SEEK_SET); |
| |
| #define BITS_PER_SAMPLE 16 |
| #define SAMPLES_PER_SECOND 16000 |
| /* http://soundfile.sapp.org/doc/WaveFormat/ */ |
| hdr.chunk_id = htole32(0x46464952); // "RIFF" |
| hdr.chunk_size = htole32(36 + totlen); |
| hdr.format = htole32(0x45564157); // "WAVE" |
| |
| hdr.subchunk1_id = htole32(0x20746d66); // "fmt " |
| hdr.subchunk1_size = htole32(16); |
| hdr.audio_format = htole16(1); |
| hdr.num_channels = htole16(1); |
| hdr.sample_rate = htole32(SAMPLES_PER_SECOND); |
| hdr.byte_rate = htole32(SAMPLES_PER_SECOND * 1 * BITS_PER_SAMPLE/8); |
| hdr.block_align = htole16(1 * BITS_PER_SAMPLE/8); |
| hdr.bits_per_sample = htole16(BITS_PER_SAMPLE); |
| |
| hdr.subchunk2_id = htole32(0x61746164); // "data" |
| hdr.subchunk2_size = htole32(totlen); |
| if ((len = write(outfd, &hdr, sizeof(WAV_hdr_t))) != sizeof(WAV_hdr_t)) { |
| fprintf(stderr, "Incorrect size for WAV header: %zd != %zd\n", |
| len, sizeof(WAV_hdr_t)); |
| exit(1); |
| } |
| |
| exit(0); |
| } |