| /* |
| * Copyright (C) 2018 Knowles Electronics |
| * |
| * 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 <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <signal.h> |
| #include <stdbool.h> |
| #include <time.h> |
| #include <errno.h> |
| #include <unistd.h> |
| |
| #define LOG_TAG "ia_tunneling_hal_test" |
| |
| #include <log/log.h> |
| #include "tunnel.h" |
| #include "conversion_routines.h" |
| #include <linux/mfd/adnc/iaxxx-system-identifiers.h> |
| #include <linux/mfd/adnc/iaxxx-tunnel-intf.h> |
| |
| |
| |
| #define MAX_TUNNELS 32 |
| #define BUF_SIZE 32768 |
| #define MAX_FILE_PATH 256 |
| |
| #define DEFAULT_PATH "/data/data" |
| #define FILE_PREFIX "/tnl_op" |
| #define UNPARSED_OUTPUT_FILE "/unparsed_output" |
| |
| struct raf_format_type { |
| uint16_t frameSizeInBytes; /*!< Frame length in bytes */ |
| uint8_t encoding; /*!< Encoding */ |
| uint8_t sampleRate; /*!< Sample rate */ |
| }; |
| |
| struct raf_frame_type { |
| uint64_t timeStamp; /*!< Timestamp of the frame */ |
| uint32_t seqNo; /*!< Optional sequence number of the frame */ |
| |
| struct raf_format_type format; /*!< Format information for the frame */ |
| uint32_t data[0]; /*!< Start of the variable size payload. |
| It must start at 128 bit aligned address for all the frames */ |
| }; |
| |
| volatile int capturing = 1; |
| |
| void sigint_handler(int sig __unused) { |
| ALOGE("Interrupted, setting the exit condition"); |
| capturing = 0; |
| } |
| |
| |
| void parse_audio_tunnel_data(FILE *out_fp, unsigned char *buf_itr, int frame_sz_in_bytes) { |
| char q16_buf[BUF_SIZE]; // This can be smaller but by how much? |
| int frameSizeInWords = (frame_sz_in_bytes + 3) >> 2; |
| |
| if (NULL == buf_itr || NULL == out_fp) { |
| ALOGE("%s: Buffer or file pointer is NULL", __func__); |
| return; |
| } |
| |
| kst_float_to_q15_vector(q16_buf, buf_itr, frameSizeInWords); |
| |
| fwrite(q16_buf, (frameSizeInWords * 2), 1, out_fp); |
| fflush(out_fp); |
| } |
| |
| int main(int argc, char *argv[]) { |
| struct ia_tunneling_hal *thdl = NULL; |
| int err = 0; |
| FILE *out_fp[MAX_TUNNELS] = { NULL }; |
| FILE *unp_out_fp = NULL; |
| int bytes_avail = 0, bytes_rem = 0; |
| int bytes_read = 0; |
| void *buf = NULL; |
| // The magic number is ROME in ASCII reversed. So we are looking for EMOR in the byte stream |
| const unsigned char magic_num[4] = {0x45, 0x4D, 0x4F, 0x52}; |
| int i = 0; |
| bool valid_frame = true; |
| char filepath[MAX_FILE_PATH]; |
| char filename[MAX_FILE_PATH]; |
| int num_of_tunnels = 0; |
| int num_tunnel_params; |
| float timer_signal = 0; |
| timer_t timer_id; |
| int tunnel_src[MAX_TUNNELS] = { 0 }; |
| int tunnel_mode[MAX_TUNNELS] = { 0 }; |
| int tunnel_encode[MAX_TUNNELS]; |
| int lastSeqNum[MAX_TUNNELS] = { 0 }; |
| int notFirstFrame[MAX_TUNNELS] = { 0 }; |
| int frameDropCount[MAX_TUNNELS] = { 0 }; |
| uint64_t tunnel_time_stamps[MAX_TUNNELS] = { 0 }; |
| unsigned char *frame_start, *buf_itr; |
| // Minimum bytes required is the magic number + tunnel id + reserved and crc + raf struct |
| int min_bytes_req = 4 + 2 + 6 + sizeof(struct raf_frame_type); |
| int instance; |
| |
| if (argc < 5) { |
| ALOGE("USAGE: %s <instance number> <Number of tunnels> <Time in seconds> <Source End pt 1> <tnl mode> <encode fmt> <Source End pt 2> <tnl mode> <encode fmt>... <output path>", argv[0]); |
| return -EINVAL; |
| } |
| |
| instance = strtol(argv[1], NULL, 0); |
| ALOGD("instance %d", instance); |
| |
| num_of_tunnels = strtol(argv[2], NULL, 0); |
| ALOGD("Number of tunnels %d", num_of_tunnels); |
| |
| timer_signal = strtof(argv[3], NULL); |
| ALOGD("tunnel out timer based req %f", timer_signal); |
| err = timer_create(CLOCK_MONOTONIC, NULL, &timer_id); |
| if (err != 0) { |
| ALOGE("Couldn't create timer: %s", strerror(errno)); |
| return -EINVAL; |
| } |
| |
| num_tunnel_params = num_of_tunnels * 3 + 4; |
| #ifdef FILENAME_ASSIGN |
| char filename_str_format[256] = {0}; |
| bool is_specified_name = false; |
| bool is_specified_path= false; |
| for (int i = 0; i < argc ; i++) { |
| if (strncmp(argv[i], "-f", sizeof(char)*2) == 0) { |
| if ((i+1) < argc) { |
| snprintf(filename_str_format, sizeof(filename_str_format), "%s", argv[i+1]); |
| ALOGE("specify a output file name %s, argc i = %d, argv[%d] = %s", filename_str_format, i, i+1, argv[i+1]); |
| is_specified_name = true; |
| } |
| } |
| if (strncmp(argv[i], "-p", sizeof(char)*2) == 0) { |
| if ((i+1) < argc) { |
| snprintf(filepath, sizeof(filepath), "%s", argv[i+1]); |
| ALOGE("specify a output file path %s, argc i = %d, argv[%d] = %s", filepath, i, i+1, argv[i+1]); |
| is_specified_path = true; |
| } |
| } |
| } |
| |
| if (is_specified_name || is_specified_path) { |
| int spec_number = 2 * ((int) is_specified_name + (int) is_specified_path); |
| |
| if (argc != (num_tunnel_params + spec_number)) { |
| ALOGE("USAGE: %s <instance number> <Number of tunnels> <Sync Tunnel req> <Source End pt 1> <tnl mode> <encode fmt> <Source End pt 2> <tnl mode> <encode fmt>... [-f filename] [-p filepath]", argv[0]); |
| return -EINVAL; |
| } |
| else { |
| ALOGE("is_specified_name = TRUE"); |
| } |
| } else { |
| if (argc != num_tunnel_params && argc != num_tunnel_params + 1) { |
| ALOGE("USAGE: %s <instance number> <Number of tunnels> <Sync Tunnel req> <Source End pt 1> <tnl mode> <encode fmt> <Source End pt 2> <tnl mode> <encode fmt>...", argv[0]); |
| return -EINVAL; |
| } |
| } |
| #else |
| if (argc != num_tunnel_params && argc != num_tunnel_params + 1) { |
| ALOGE("USAGE: %s <instance number> <Number of tunnels> <Sync Tunnel req> <Source End pt 1> <tnl mode> <encode fmt> <Source End pt 2> <tnl mode> <encode fmt>...", argv[0]); |
| return -EINVAL; |
| } |
| #endif /* FILENAME_ASSIGN */ |
| |
| |
| for (i = 0; i < num_of_tunnels; i++) { |
| tunnel_src[i] = strtol(argv[i*3+4], NULL, 0); |
| tunnel_mode[i] = strtol(argv[i*3+5], NULL, 0); |
| tunnel_encode[i] = strtol(argv[i*3+6], NULL, 0); |
| ALOGD("Tunnel source 0x%x Tunnel mode %d Tunnel encode %d", tunnel_src[i], tunnel_mode[i], tunnel_encode[i]); |
| } |
| |
| #ifdef FILENAME_ASSIGN |
| if (!is_specified_path) { |
| strcpy(filepath, DEFAULT_PATH); |
| } |
| #else |
| if (argc == num_tunnel_params) { |
| strcpy(filepath, DEFAULT_PATH); |
| } else { |
| strcpy(filepath, argv[argc-1]); |
| } |
| #endif |
| ALOGE("Output path %s", filepath); |
| |
| thdl = ia_start_tunneling(0); |
| if (NULL == thdl) { |
| ALOGE("Failed to start tunneling"); |
| goto exit; |
| } |
| |
| for (i = 0; i < num_of_tunnels; i++) { |
| err = ia_enable_tunneling_source(thdl, tunnel_src[i], tunnel_mode[i], tunnel_encode[i]); |
| if (0 != err) { |
| ALOGE("Failed to enable tunneling for src_id %u mode %u encode %u", tunnel_src[i], tunnel_mode[i], tunnel_encode[i]); |
| goto exit; |
| } |
| } |
| |
| buf = malloc(BUF_SIZE * 2); |
| if (NULL == buf) { |
| ALOGE("Failed to allocate memory to read buffer"); |
| goto exit; |
| } |
| |
| snprintf(filename, MAX_FILE_PATH, "%s%s", filepath, UNPARSED_OUTPUT_FILE); |
| unp_out_fp = fopen(filename, "wb"); |
| if (NULL == unp_out_fp) { |
| ALOGE("Failed to open the file %s", filename); |
| goto exit; |
| } |
| |
| signal(SIGINT, sigint_handler); |
| |
| if (num_of_tunnels && timer_signal) { |
| struct itimerspec timer_spec; |
| uint64_t timer_ns = timer_signal * NS_PER_SEC; |
| |
| signal(SIGALRM, sigint_handler); |
| |
| memset(&timer_spec, 0, sizeof(timer_spec)); |
| timer_spec.it_value.tv_sec = timer_ns / NS_PER_SEC; |
| timer_spec.it_value.tv_nsec = timer_ns % NS_PER_SEC; |
| timer_settime(timer_id, 0, &timer_spec, NULL); |
| ALOGD("timer_settime %ld %ld", |
| timer_spec.it_value.tv_sec, |
| timer_spec.it_value.tv_nsec); |
| } |
| |
| unsigned short int tunnel_id; |
| unsigned short int tunl_src; |
| while (1) { |
| read_again: |
| if (0 == capturing) { |
| ALOGE("Time to bail from here"); |
| break; |
| } |
| |
| if (0 != bytes_avail) { |
| if (bytes_avail < 0) { |
| bytes_rem = 0; |
| } else { |
| bytes_rem = bytes_avail; |
| ALOGD("bytes_avail is %d", bytes_rem); |
| memcpy(buf, buf_itr, bytes_rem); |
| } |
| } else { |
| bytes_rem = 0; |
| } |
| |
| // Ensure that we read BUF_SIZE always otherwise the kernel read will hang |
| bytes_avail = ia_read_tunnel_data (thdl, |
| (void *)((unsigned char *)buf + bytes_rem), |
| BUF_SIZE); |
| if (bytes_avail <= 0) { |
| ALOGE("Failed to read data from the tunnel :%d", bytes_avail); |
| break; |
| } |
| |
| fwrite((void *)((unsigned char *)buf + bytes_rem), bytes_avail, 1, unp_out_fp); |
| fflush(unp_out_fp); |
| |
| bytes_avail += bytes_rem; // update the available bytes with the previous reminder if any |
| ALOGD("bytes_avail is after read %d", bytes_avail); |
| buf_itr = (unsigned char *)buf; |
| |
| do { |
| // Check for MagicNumber 0x454D4F52 |
| if (buf_itr[0] != magic_num[0] || buf_itr[1] != magic_num[1] || |
| buf_itr[2] != magic_num[2] || buf_itr[3] != magic_num[3]) { |
| ALOGE("Could not find the magic number, reading again"); |
| ALOGE("buf_itr[0] %x buf_itr[1] %x buf_itr[2] %x buf_itr[3] %x ", |
| buf_itr[0], buf_itr[1], buf_itr[2], buf_itr[3]); |
| goto exit; |
| } |
| ALOGD("bytes_avail is after magic %d: prev :%d", bytes_avail, bytes_avail + 540); |
| // Bookmark the start of the frame |
| frame_start = buf_itr; |
| |
| // Skip the magic number |
| buf_itr += 4; |
| bytes_avail -= 4; |
| |
| // Read the tunnelID |
| tunnel_id = ((unsigned char) (buf_itr[0]) | |
| (unsigned char) (buf_itr[1]) << 8); |
| |
| // Skip tunnelID |
| buf_itr += 2; |
| bytes_avail -= 2; |
| |
| tunl_src = ((unsigned char) (buf_itr[0]) | |
| (unsigned char) (buf_itr[1]) << 8); |
| |
| // Skip src id field and CRC - 6 bytes in total |
| buf_itr += 6; |
| bytes_avail -= 6; |
| |
| valid_frame = true; |
| if (tunnel_id > MAX_TUNNELS) { |
| ALOGE("Invalid tunnel id %d", tunnel_id); |
| valid_frame = false; |
| } |
| |
| struct raf_frame_type rft; |
| memcpy(&rft, buf_itr, sizeof(struct raf_frame_type)); |
| if (true == valid_frame) { |
| if (NULL == out_fp[tunnel_id]) { |
| #ifdef FILENAME_ASSIGN |
| if (is_specified_name) { |
| snprintf(filename, 256, "%s/%s", filepath, filename_str_format); |
| } else if (TNL_ENC_OPAQUE == rft.format.encoding) { |
| #else |
| if (TNL_ENC_OPAQUE == rft.format.encoding) { |
| #endif /* FILENAME_ASSIGN */ |
| snprintf(filename, MAX_FILE_PATH, "%s%sid%d-src0x%x-enc0x%x_client%d.raw", filepath, FILE_PREFIX, tunnel_id, tunl_src, rft.format.encoding, instance); |
| } else { |
| snprintf(filename, MAX_FILE_PATH, "%s%sid%d-src0x%x-enc0x%x_client%d.pcm", filepath, FILE_PREFIX, tunnel_id, tunl_src, rft.format.encoding, instance); |
| } |
| // Open the file to dump |
| out_fp[tunnel_id] = fopen(filename, "wb"); |
| if (NULL == out_fp[tunnel_id]) { |
| ALOGE("ERROR: Failed to open the file %s", filename); |
| goto exit; |
| } |
| } |
| } |
| |
| ALOGD("Tunnel id %d timestamp %llu", tunnel_id, rft.timeStamp); |
| tunnel_time_stamps[tunnel_id] = rft.timeStamp; |
| |
| // Skip the raf_frame_type |
| buf_itr += sizeof(struct raf_frame_type); |
| bytes_avail -= sizeof(struct raf_frame_type); |
| |
| if (bytes_avail < rft.format.frameSizeInBytes) { |
| ALOGD("Incomplete frame received bytes_avail %d framesize %d", bytes_avail, rft.format.frameSizeInBytes); |
| buf_itr = frame_start; |
| bytes_avail += min_bytes_req; |
| goto read_again; |
| } |
| |
| if (true == valid_frame) { |
| ALOGD("@@@Tunnel id %d encoding %d", tunnel_id, rft.format.encoding); |
| if (TNL_ENC_AFLOAT == rft.format.encoding) { |
| parse_audio_tunnel_data(out_fp[tunnel_id], buf_itr, rft.format.frameSizeInBytes); |
| } else { |
| fwrite(buf_itr, rft.format.frameSizeInBytes, 1, out_fp[tunnel_id]); |
| } |
| } |
| |
| /* Calculate the frame drop count */ |
| if (notFirstFrame[tunnel_id]) { |
| frameDropCount[tunnel_id] += (rft.seqNo - lastSeqNum[tunnel_id] - 1); |
| } |
| lastSeqNum[tunnel_id] = rft.seqNo; |
| notFirstFrame[tunnel_id] = 1; |
| // Skip the data |
| buf_itr += rft.format.frameSizeInBytes; |
| bytes_avail -= rft.format.frameSizeInBytes; |
| /* For debugging the tunnel read errors or wrong magic numbers or bus errors*/ |
| bytes_read += rft.format.frameSizeInBytes + min_bytes_req; |
| } while (bytes_avail > min_bytes_req); |
| } |
| |
| exit: |
| for (i = 0; i < MAX_TUNNELS; i++) { |
| if (notFirstFrame[i]) { |
| ALOGE("drop count tunnel id %u: %u", i, frameDropCount[i]); |
| } |
| } |
| ALOGE("bytes_read so far %d", bytes_read); |
| if (buf) { |
| free(buf); |
| buf = NULL; |
| } |
| |
| if (unp_out_fp) { |
| fflush(unp_out_fp); |
| fclose(unp_out_fp); |
| } |
| |
| for (i = 0; i < MAX_TUNNELS; i++) { |
| if (out_fp[i]) { |
| fflush(out_fp[i]); |
| fclose(out_fp[i]); |
| } |
| } |
| |
| for (i = 0; i < num_of_tunnels; i++) { |
| err = ia_disable_tunneling_source(thdl, tunnel_src[i], tunnel_mode[i], tunnel_encode[i]); |
| if (0 != err) { |
| ALOGE("Failed to disable tunneling for tunl_id %u src_id %u", i, tunnel_src[i]); |
| } |
| } |
| |
| err = ia_stop_tunneling(thdl); |
| if (0 != err) { |
| ALOGE("Failed to stop tunneling"); |
| } |
| |
| timer_delete(timer_id); |
| |
| return 0; |
| } |