Commit bb4878ad authored by Mikhail Karpenko's avatar Mikhail Karpenko

Add basic audio functions

This commit starts to add audio recording to camogm. Basic code for ALSA
initialization and audio samples reading is added but recording to file
has not been implemented yet.
parent 23bac812
...@@ -4,19 +4,19 @@ TEST_PROG = camogm_test ...@@ -4,19 +4,19 @@ TEST_PROG = camogm_test
PHPSCRIPTS = camogmstate.php $(GUIDIR)/camogmgui.php $(GUIDIR)/camogmgui.css $(GUIDIR)/camogmgui.js $(GUIDIR)/camogm_interface.php \ PHPSCRIPTS = camogmstate.php $(GUIDIR)/camogmgui.php $(GUIDIR)/camogmgui.css $(GUIDIR)/camogmgui.js $(GUIDIR)/camogm_interface.php \
$(GUIDIR)/SpryTabbedPanels.css $(GUIDIR)/SpryTabbedPanels.js $(GUIDIR)/xml_simple.php $(GUIDIR)/SpryCollapsiblePanel.css \ $(GUIDIR)/SpryTabbedPanels.css $(GUIDIR)/SpryTabbedPanels.js $(GUIDIR)/xml_simple.php $(GUIDIR)/SpryCollapsiblePanel.css \
$(GUIDIR)/SpryCollapsiblePanel.js $(GUIDIR)/SpryCollapsiblePanel.js
CONFIGS = qt_source CONFIGS = qt_source qt_audio
IMAGES = $(GUIDIR)/images/filebrowser-01.gif $(GUIDIR)/images/filebrowser-bottom-01.gif $(GUIDIR)/images/png_white_30.png \ IMAGES = $(GUIDIR)/images/filebrowser-01.gif $(GUIDIR)/images/filebrowser-bottom-01.gif $(GUIDIR)/images/png_white_30.png \
$(GUIDIR)/images/record.gif $(GUIDIR)/images/reload.png $(GUIDIR)/images/stop.gif $(GUIDIR)/images/create_folder.png \ $(GUIDIR)/images/record.gif $(GUIDIR)/images/reload.png $(GUIDIR)/images/stop.gif $(GUIDIR)/images/create_folder.png \
$(GUIDIR)/images/divider.png $(GUIDIR)/images/folder.gif $(GUIDIR)/images/help.png $(GUIDIR)/images/quicktime.png \ $(GUIDIR)/images/divider.png $(GUIDIR)/images/folder.gif $(GUIDIR)/images/help.png $(GUIDIR)/images/quicktime.png \
$(GUIDIR)/images/rec_folder.png $(GUIDIR)/images/up_folder.gif $(GUIDIR)/images/play_audio.png $(GUIDIR)/images/hdd.png $(GUIDIR)/images/rec_folder.png $(GUIDIR)/images/up_folder.gif $(GUIDIR)/images/play_audio.png $(GUIDIR)/images/hdd.png
SRCS = camogm.c camogm_ogm.c camogm_jpeg.c camogm_mov.c camogm_kml.c camogm_read.c index_list.c camogm_align.c SRCS = camogm.c camogm_ogm.c camogm_jpeg.c camogm_mov.c camogm_kml.c camogm_read.c index_list.c camogm_align.c camogm_audio.c thelper.c
TEST_SRC = camogm_test.c TEST_SRC = camogm_test.c
OBJS = $(SRCS:.c=.o) OBJS = $(SRCS:.c=.o)
CFLAGS += -Wall -I$(STAGING_DIR_HOST)/usr/include-uapi CFLAGS += -Wall -I$(STAGING_DIR_HOST)/usr/include-uapi
LDLIBS += -logg -pthread -lm LDLIBS += -logg -pthread -lm -lasound
INSTALL = install INSTALL = install
INSTMODE = 0755 INSTMODE = 0755
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/uio.h> #include <sys/uio.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h>
#include <string.h> #include <string.h>
#include <getopt.h> #include <getopt.h>
#include <ctype.h> #include <ctype.h>
...@@ -35,6 +36,7 @@ ...@@ -35,6 +36,7 @@
#include "camogm_mov.h" #include "camogm_mov.h"
#include "camogm_kml.h" #include "camogm_kml.h"
#include "camogm_read.h" #include "camogm_read.h"
#include "thelper.h"
/** @brief Default debug level */ /** @brief Default debug level */
#define DEFAULT_DEBUG_LVL 6 #define DEFAULT_DEBUG_LVL 6
...@@ -61,6 +63,10 @@ ...@@ -61,6 +63,10 @@
#define ALL_CHN_INACTIVE 0x00 #define ALL_CHN_INACTIVE 0x00
/** @brief This is a basic helper macro for processing all sensor ports at a time */ /** @brief This is a basic helper macro for processing all sensor ports at a time */
#define FOR_EACH_PORT(indtype, indvar) for (indtype indvar = 0; indvar < SENSOR_PORTS; indvar++) #define FOR_EACH_PORT(indtype, indvar) for (indtype indvar = 0; indvar < SENSOR_PORTS; indvar++)
/** @brief container_of macro as in the kernel */
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
char trailer[TRAILER_SIZE] = { 0xff, 0xd9 }; char trailer[TRAILER_SIZE] = { 0xff, 0xd9 };
...@@ -112,8 +118,8 @@ int debug_level; ...@@ -112,8 +118,8 @@ int debug_level;
FILE* debug_file; FILE* debug_file;
void camogm_init(camogm_state *state, char *pipe_name, uint16_t port_num); void camogm_init(camogm_state *state, char *pipe_name, uint16_t port_num);
int camogm_start(camogm_state *state); int camogm_start(camogm_state *state, bool restart);
int camogm_stop(camogm_state *state); int camogm_stop(camogm_state *state, bool reset);
void camogm_reset(camogm_state *state); void camogm_reset(camogm_state *state);
int camogm_debug(camogm_state *state, const char *fname); int camogm_debug(camogm_state *state, const char *fname);
int camogm_debug_level(int d); int camogm_debug_level(int d);
...@@ -152,6 +158,7 @@ static int get_disk_range(const char *name, struct range *rng); ...@@ -152,6 +158,7 @@ static int get_disk_range(const char *name, struct range *rng);
static int set_disk_range(const struct range *rng); static int set_disk_range(const struct range *rng);
static void get_disk_info(camogm_state *state); static void get_disk_info(camogm_state *state);
static struct timeval get_fpga_time(const int fd_fparsall, unsigned int port); static struct timeval get_fpga_time(const int fd_fparsall, unsigned int port);
static void get_fpga_time_w(const struct audio *audio, struct timeval *tv);
int open_files(camogm_state *state); int open_files(camogm_state *state);
unsigned long getGPValue(unsigned int port, unsigned long GPNumber); unsigned long getGPValue(unsigned int port, unsigned long GPNumber);
void setGValue(unsigned int port, unsigned long GNumber, unsigned long value); void setGValue(unsigned int port, unsigned long GNumber, unsigned long value);
...@@ -162,6 +169,10 @@ void clean_up(camogm_state *state); ...@@ -162,6 +169,10 @@ void clean_up(camogm_state *state);
static void camogm_err_stat(const camogm_state *state, int port, FILE *f, bool xml); static void camogm_err_stat(const camogm_state *state, int port, FILE *f, bool xml);
static void camogm_set_dummy_read(camogm_state *state, int d); static void camogm_set_dummy_read(camogm_state *state, int d);
static void camogm_set_audio_state(camogm_state *state, char *args);
static void camogm_set_audio_volume(camogm_state *state, char *args);
static void camogm_set_audio_format(camogm_state *state, char *args);
void put_uint16(void *buf, u_int16_t val) void put_uint16(void *buf, u_int16_t val)
{ {
unsigned char *tmp; unsigned char *tmp;
...@@ -271,6 +282,13 @@ void camogm_init(camogm_state *state, char *pipe_name, uint16_t port_num) ...@@ -271,6 +282,13 @@ void camogm_init(camogm_state *state, char *pipe_name, uint16_t port_num)
state->writer_params.state = STATE_STOPPED; state->writer_params.state = STATE_STOPPED;
camogm_set_dummy_read(state, 0); camogm_set_dummy_read(state, 0);
// audio stuff
state->audio.dev_name = DEFAULT_SND_DEVICE;
state->audio.set_audio_channels = SAMPLE_CHANNELS;
state->audio.set_audio_rate = SAMPLE_RATE;
state->audio.set_audio_volume = DEFAULT_AUDIO_VOLUME;
state->audio.get_fpga_time = get_fpga_time_w;
} }
/** /**
...@@ -311,9 +329,10 @@ int camogm_debug_level(int d) ...@@ -311,9 +329,10 @@ int camogm_debug_level(int d)
/** /**
* @brief Start recording for each active sensor port * @brief Start recording for each active sensor port
* @param[in] state a pointer to a structure containing current state * @param[in] state a pointer to a structure containing current state
* @param[in] restart flag indicating that full restart is requested
* @return 0 if the recording finished successfully and negative error code otherwise * @return 0 if the recording finished successfully and negative error code otherwise
*/ */
int camogm_start(camogm_state *state) int camogm_start(camogm_state *state, bool restart)
{ {
int timestamp_start; int timestamp_start;
int rslt; int rslt;
...@@ -338,13 +357,14 @@ int camogm_start(camogm_state *state) ...@@ -338,13 +357,14 @@ int camogm_start(camogm_state *state)
int * ifp_this = (int*)&(state->this_frame_params[port]); int * ifp_this = (int*)&(state->this_frame_params[port]);
if (state->kml_enable) camogm_init_kml(); // do nothing if (state->kml_enable) camogm_init_kml(); // do nothing
audio_init(&state->audio, restart);
if (state->format != state->set_format) { if (state->format != state->set_format) {
state->format = state->set_format; state->format = state->set_format;
switch (state->format) { switch (state->format) {
case CAMOGM_FORMAT_NONE: rslt = 0; break; case CAMOGM_FORMAT_NONE: rslt = 0; break;
case CAMOGM_FORMAT_OGM: rslt = camogm_init_ogm(); break; case CAMOGM_FORMAT_OGM: rslt = camogm_init_ogm(); break;
case CAMOGM_FORMAT_JPEG: rslt = camogm_init_jpeg(state); break; case CAMOGM_FORMAT_JPEG: rslt = camogm_init_jpeg(state); break;
case CAMOGM_FORMAT_MOV: rslt = camogm_init_mov(); break; case CAMOGM_FORMAT_MOV: rslt = camogm_init_mov(state); break;
} }
state->formats |= 1 << (state->format); state->formats |= 1 << (state->format);
// exit on unknown formats? // exit on unknown formats?
...@@ -482,7 +502,8 @@ int camogm_start(camogm_state *state) ...@@ -482,7 +502,8 @@ int camogm_start(camogm_state *state)
} }
} }
// here we are ready to initialize Ogm (or other) file // here we are ready to initialize Ogm (or other) file
audio_start(&state->audio);
switch (state->format) { switch (state->format) {
case CAMOGM_FORMAT_NONE: rslt = 0; break; case CAMOGM_FORMAT_NONE: rslt = 0; break;
case CAMOGM_FORMAT_OGM: rslt = camogm_start_ogm(state); break; case CAMOGM_FORMAT_OGM: rslt = camogm_start_ogm(state); break;
...@@ -519,9 +540,6 @@ int sendImageFrame(camogm_state *state) ...@@ -519,9 +540,6 @@ int sendImageFrame(camogm_state *state)
int * ifp_this = (int*)&(state->this_frame_params[state->port_num]); int * ifp_this = (int*)&(state->this_frame_params[state->port_num]);
int fp; int fp;
int port = state->port_num; int port = state->port_num;
struct timeval start_time, end_time;
// start_time = get_fpga_time(state->fd_fparmsall[port], port);
// This is probably needed only for Quicktime (not to exceed already allocated frame index) // This is probably needed only for Quicktime (not to exceed already allocated frame index)
if (!state->rawdev_op && (state->frameno >= (state->max_frames))) { if (!state->rawdev_op && (state->frameno >= (state->max_frames))) {
...@@ -666,12 +684,68 @@ int sendImageFrame(camogm_state *state) ...@@ -666,12 +684,68 @@ int sendImageFrame(camogm_state *state)
state->packetchunks[state->chunk_index ].bytes = 2; state->packetchunks[state->chunk_index ].bytes = 2;
state->packetchunks[state->chunk_index++].chunk = (unsigned char*)trailer; state->packetchunks[state->chunk_index++].chunk = (unsigned char*)trailer;
switch (state->format) { state->audio.ts_video.tv_sec = state->this_frame_params[port].timestamp_sec;
case CAMOGM_FORMAT_NONE: rslt = 0; break; state->audio.ts_video.tv_usec = state->this_frame_params[port].timestamp_usec;
case CAMOGM_FORMAT_OGM: rslt = camogm_frame_ogm(state); break; int sync_ok = 1;
case CAMOGM_FORMAT_JPEG: rslt = camogm_frame_jpeg(state); break; // synchronize audio and video before recording has started, this need to be done only once
case CAMOGM_FORMAT_MOV: rslt = camogm_frame_mov(state); break; if (state->audio.ctx_a.begin_of_stream_with_audio) {
default: rslt = 0; // do nothing D6(fprintf(debug_file, "\n"));
if (state->audio.ctx_a.audio_trigger) {
state->audio.ts_video_start = state->audio.ts_audio;
state->audio.ts_video_start.tv_usec += state->frame_period[port] / 2;
time_normalize(&state->audio.ts_video_start);
state->audio.ctx_a.audio_trigger = 0;
// calculate how many audio samples we need to skip here
long audio_to_skip = 0;
struct timeval tv = state->audio.ts_video;
while (time_comp(&tv, &state->audio.ts_video_start) < 0) {
tv.tv_usec += state->frame_period[port];
time_normalize(&tv);
}
tv.tv_sec -= 1;
tv.tv_usec += 1000000;
tv.tv_usec -= state->frame_period[port] / 2;
time_normalize(&tv);
if (tv.tv_sec != state->audio.ts_audio.tv_sec) {
audio_to_skip = tv.tv_sec - state->audio.ts_audio.tv_sec;
audio_to_skip *= 1000000;
}
audio_to_skip += tv.tv_usec;
audio_to_skip -= state->audio.ts_audio.tv_usec;
double s = state->audio.audio_rate;
s /= 1000.0;
s *= audio_to_skip;
s /= 1000.0;
state->audio.ctx_a.audio_skip_samples = (long) s;
state->audio.ctx_a.time_start = tv;
D6(fprintf(debug_file , "audio started at: %ld:%06ld; we need to record it from: %ld:%06ld; audio_to_skip_us == %ld; "
"audio samples to skip == %lld\n",
state->audio.ts_audio.tv_sec, state->audio.ts_audio.tv_usec, tv.tv_sec, tv.tv_usec, audio_to_skip,
state->audio.ctx_a.audio_skip_samples));
}
D6(fprintf(debug_file, "audio (start): %ld:%06ld; video (current): %ld:%06ld; frame period is: %d us\n",
state->audio.ts_audio.tv_sec, state->audio.ts_audio.tv_usec, state->audio.ts_video.tv_sec, state->audio.ts_video.tv_usec,
state->frame_period[port]));
if (time_comp(&state->audio.ts_video_start, &state->audio.ts_video) > 0) {
D6(fprintf(debug_file, "skip this video frame\n"));
sync_ok = 0;
} else {
D6(fprintf(debug_file, "save video frame with time: %ld:%06ld\n", state->audio.ts_video.tv_sec, state->audio.ts_video.tv_usec));
state->audio.ctx_a.begin_of_stream_with_audio = 0;
}
}
if (sync_ok) {
switch (state->format) {
case CAMOGM_FORMAT_NONE: rslt = 0; break;
case CAMOGM_FORMAT_OGM: rslt = camogm_frame_ogm(state); break;
case CAMOGM_FORMAT_JPEG: rslt = camogm_frame_jpeg(state); break;
case CAMOGM_FORMAT_MOV: rslt = camogm_frame_mov(state); break;
default: rslt = 0; // do nothing
}
} else {
// skip only first video frames that are ahead of audio stream
rslt = 0;
} }
if (rslt) { if (rslt) {
D3(fprintf(debug_file, "sendImageFrame:12: camogm_frame_***() returned %d\n", rslt)); D3(fprintf(debug_file, "sendImageFrame:12: camogm_frame_***() returned %d\n", rslt));
...@@ -681,11 +755,12 @@ int sendImageFrame(camogm_state *state) ...@@ -681,11 +755,12 @@ int sendImageFrame(camogm_state *state)
if (rslt) return rslt; if (rslt) return rslt;
D3(fprintf(debug_file, "_14_")); D3(fprintf(debug_file, "_14_"));
// advance frame pointer // advance frame pointer
state->frameno++; if (sync_ok)
state->frameno++;
state->cirbuf_rp[port] = lseek(state->fd_circ[port], LSEEK_CIRC_NEXT, SEEK_END); state->cirbuf_rp[port] = lseek(state->fd_circ[port], LSEEK_CIRC_NEXT, SEEK_END);
D3(fprintf(debug_file, "\tcompressed frame number: %li\t", lseek(state->fd_circ[port], LSEEK_CIRC_GETFRAME, SEEK_END))); D3(fprintf(debug_file, "\tcompressed frame number: %li\t", lseek(state->fd_circ[port], LSEEK_CIRC_GETFRAME, SEEK_END)));
// optionally save it to global read pointer (i.e. for debugging with imgsrv "/pointers") // optionally save it to global read pointer (i.e. for debugging with imgsrv "/pointers")
if (state->save_gp) lseek(state->fd_circ[port], LSEEK_CIRC_SETP, SEEK_END); if (state->save_gp) lseek(state->fd_circ[port], LSEEK_CIRC_SETP, SEEK_END);
D3(fprintf(debug_file, "_15_\n")); D3(fprintf(debug_file, "_15_\n"));
if (state->frames_skip > 0) { if (state->frames_skip > 0) {
...@@ -695,17 +770,6 @@ int sendImageFrame(camogm_state *state) ...@@ -695,17 +770,6 @@ int sendImageFrame(camogm_state *state)
} }
D3(fprintf(debug_file,"cirbuf_rp to next frame = 0x%x\n", state->cirbuf_rp[port])); D3(fprintf(debug_file,"cirbuf_rp to next frame = 0x%x\n", state->cirbuf_rp[port]));
// end_time = get_fpga_time(state->fd_fparmsall[port], port);
// unsigned int mbps; // write speed, MB/s
// unsigned long long time_diff; // time elapsed, in microseconds
// time_diff = ((end_time.tv_sec * 1000000 + end_time.tv_usec) - (start_time.tv_sec * 1000000 + start_time.tv_usec));
// mbps = ((double)state->rawdev.last_jpeg_size / (double)1048576) / ((double)time_diff / (double)1000000);
// D6(fprintf(debug_file, "Frame start time: %ld:%ld; frame end time: %ld:%ld; last frame size: %lu\n",
// start_time.tv_sec, start_time.tv_usec,
// end_time.tv_sec, end_time.tv_usec,
// state->rawdev.last_jpeg_size));
// D6(fprintf(debug_file, "Write speed: %d MB/s\n", mbps));
return 0; return 0;
} }
...@@ -713,9 +777,10 @@ int sendImageFrame(camogm_state *state) ...@@ -713,9 +777,10 @@ int sendImageFrame(camogm_state *state)
* @brief Stop current recording. If recording was not started, this function has no effect. This function * @brief Stop current recording. If recording was not started, this function has no effect. This function
* calls @e camogm_end_* for the current file format. * calls @e camogm_end_* for the current file format.
* @param[in] state a pointer to a structure containing current state * @param[in] state a pointer to a structure containing current state
* @param[in] reset flag indicating that sound HW should be reset as well
* @return 0 if the operation was stopped successfully and negative error code otherwise * @return 0 if the operation was stopped successfully and negative error code otherwise
*/ */
int camogm_stop(camogm_state *state) int camogm_stop(camogm_state *state, bool reset)
{ {
int rslt = 0; int rslt = 0;
...@@ -730,16 +795,18 @@ int camogm_stop(camogm_state *state) ...@@ -730,16 +795,18 @@ int camogm_stop(camogm_state *state)
} }
return 0; return 0;
} }
D1(fprintf(debug_file, "Ending recording\n")); D1(fprintf(debug_file, "Stop recording\n"));
if (state->kml_used) camogm_end_kml(state); if (state->kml_used) camogm_end_kml(state);
if (state->audio.audio_enable)
audio_finish(&state->audio, reset);
switch (state->format) { switch (state->format) {
case CAMOGM_FORMAT_NONE: rslt = 0; break; case CAMOGM_FORMAT_NONE: rslt = 0; break;
case CAMOGM_FORMAT_OGM: rslt = camogm_end_ogm(state); break; case CAMOGM_FORMAT_OGM: rslt = camogm_end_ogm(state); break;
case CAMOGM_FORMAT_JPEG: rslt = camogm_end_jpeg(state); break; case CAMOGM_FORMAT_JPEG: rslt = camogm_end_jpeg(state); break;
case CAMOGM_FORMAT_MOV: rslt = camogm_end_mov(state); break; case CAMOGM_FORMAT_MOV: rslt = camogm_end_mov(state); break;
// default: return 0; // do nothing // default: return 0; // do nothing
} }
// now close video file (if it is open) // now close video file (if it is open)
if (state->vf) fclose(state->vf); if (state->vf) fclose(state->vf);
state->vf = NULL; state->vf = NULL;
if (rslt) return rslt; if (rslt) return rslt;
...@@ -935,7 +1002,7 @@ void camogm_set_format(camogm_state *state, int d) ...@@ -935,7 +1002,7 @@ void camogm_set_format(camogm_state *state, int d)
case CAMOGM_FORMAT_NONE: rslt = 0; break; case CAMOGM_FORMAT_NONE: rslt = 0; break;
case CAMOGM_FORMAT_OGM: rslt = camogm_init_ogm(); break; case CAMOGM_FORMAT_OGM: rslt = camogm_init_ogm(); break;
case CAMOGM_FORMAT_JPEG: rslt = camogm_init_jpeg(state); break; case CAMOGM_FORMAT_JPEG: rslt = camogm_init_jpeg(state); break;
case CAMOGM_FORMAT_MOV: rslt = camogm_init_mov(); break; case CAMOGM_FORMAT_MOV: rslt = camogm_init_mov(state); break;
} }
if (rslt) { if (rslt) {
D0(fprintf(debug_file, "%s:%d: Error setting format to=%d\n", __FILE__, __LINE__, state->format)); D0(fprintf(debug_file, "%s:%d: Error setting format to=%d\n", __FILE__, __LINE__, state->format));
...@@ -966,6 +1033,65 @@ void camogm_set_start_after_timestamp(camogm_state *state, double d) ...@@ -966,6 +1033,65 @@ void camogm_set_start_after_timestamp(camogm_state *state, double d)
state->start_after_timestamp = d; state->start_after_timestamp = d;
} }
/** @brief Change audio processing state */
static void camogm_set_audio_state(camogm_state *state, char *args)
{
if (strncmp(args, "on", 2) == 0 || strncmp(args, "enable", 6) == 0) {
state->audio.set_audio_enable = 1;
} else if (strncmp(args, "off", 3) == 0 || strncmp(args, "disable", 7) == 0){
state->audio.set_audio_enable = 0;
}
}
/** @brief Set audio volume */
static void camogm_set_audio_volume(camogm_state *state, char *args)
{
int vol = atoi(args);
if (vol > 65535)
vol = 65535;
if (vol < 0)
vol = 0;
state->audio.set_audio_volume = vol;
}
/** @brief Set audio sample rate and number of channels */
static void camogm_set_audio_format(camogm_state *state, char *args)
{
char buf[8];
int i;
int channels = -1;
int rate = -1;
char *right = strstr(args, "/");
if (right != NULL) {
channels = atol(&right[1]);
if (channels < AUDIO_CHANNELS_MIN)
channels = AUDIO_CHANNELS_MIN;
if (channels > AUDIO_CHANNELS_MAX)
channels = AUDIO_CHANNELS_MAX;
state->audio.set_audio_channels = channels;
}
for (i = 0; i < 7; i++) {
if (args[i] != '\0' && args[i] != '/')
buf[i] = args[i];
else
break;
}
buf[i] = '\0';
if (buf[0] != '\0') {
rate = atol(buf);
if (rate < AUDIO_RATE_MIN)
rate = AUDIO_RATE_MIN;
if (rate > AUDIO_RATE_MAX)
rate = AUDIO_RATE_MAX;
state->audio.set_audio_rate = rate;
}
D6(fprintf(debug_file, "request for audio format: %s; set rate = %d, channels = %d\n",
args, state->audio.set_audio_rate, state->audio.set_audio_channels));
}
/** /**
* @brief Print current status either as plain text or in xml format * @brief Print current status either as plain text or in xml format
* @param[in] state to a structure containing current state * @param[in] state to a structure containing current state
...@@ -987,6 +1113,7 @@ void camogm_status(camogm_state *state, char * fn, int xml) ...@@ -987,6 +1113,7 @@ void camogm_status(camogm_state *state, char * fn, int xml)
int _frames_skip = 0; int _frames_skip = 0;
int _sec_skip = 0; int _sec_skip = 0;
char *_kml_enable, *_kml_used, *_kml_height_mode; char *_kml_enable, *_kml_used, *_kml_height_mode;
char *_audio_en;
unsigned int _percent_done; unsigned int _percent_done;
off_t save_p; off_t save_p;
...@@ -1069,6 +1196,7 @@ void camogm_status(camogm_state *state, char * fn, int xml) ...@@ -1069,6 +1196,7 @@ void camogm_status(camogm_state *state, char * fn, int xml)
else else
_percent_done = 0; _percent_done = 0;
_audio_en = state->audio.set_audio_enable ? "yes" : "no";
if (xml) { if (xml) {
fprintf(f, "<?xml version=\"1.0\"?>\n" \ fprintf(f, "<?xml version=\"1.0\"?>\n" \
"<camogm_state>\n" \ "<camogm_state>\n" \
...@@ -1112,18 +1240,25 @@ void camogm_status(camogm_state *state, char * fn, int xml) ...@@ -1112,18 +1240,25 @@ void camogm_status(camogm_state *state, char * fn, int xml)
" <raw_device_pos_read>0x%llx (%d%% done)</raw_device_pos_read>\n" \ " <raw_device_pos_read>0x%llx (%d%% done)</raw_device_pos_read>\n" \
" <lba_start>%llu</lba_start>\n" \ " <lba_start>%llu</lba_start>\n" \
" <lba_current>%llu</lba_current>\n" \ " <lba_current>%llu</lba_current>\n" \
" <lba_end>%llu</lba_end>\n", " <lba_end>%llu</lba_end>\n" \
_state, state->path, state->frameno, state->start_after_timestamp, _dur, _udur, _len, \
_frames_skip, _sec_skip, \ " <audio_enable>%s</audio_enable>\n" \
state->width, state->height, _output_format, _using_exif, \ " <audio_channels>%d</audio_channels>\n" \
state->path_prefix, state->segment_duration, state->segment_length, state->max_frames, state->timescale, \ " <audio_rate>%d</audio_rate>\n" \
state->frames_per_chunk, state->last_error_code, \ " <audio_volume>%d</audio_volume>\n",
state->debug_name, debug_level, _using_global_pointer, \ _state, state->path, state->frameno, state->start_after_timestamp, _dur, _udur, _len,
_kml_enable, _kml_used, state->kml_path, state->kml_horHalfFov, state->kml_vertHalfFov, state->kml_near, \ _frames_skip, _sec_skip,
_kml_height_mode, state->kml_height, state->kml_period, state->kml_last_ts, state->kml_last_uts, \ state->width, state->height, _output_format, _using_exif,
state->path_prefix, state->segment_duration, state->segment_length, state->max_frames, state->timescale,
state->frames_per_chunk, state->last_error_code,
state->debug_name, debug_level, _using_global_pointer,
_kml_enable, _kml_used, state->kml_path, state->kml_horHalfFov, state->kml_vertHalfFov, state->kml_near,
_kml_height_mode, state->kml_height, state->kml_period, state->kml_last_ts, state->kml_last_uts,
state->greedy ? "yes" : "no", state->ignore_fps ? "yes" : "no", state->rawdev.rawdev_path, state->greedy ? "yes" : "no", state->ignore_fps ? "yes" : "no", state->rawdev.rawdev_path,
state->rawdev.overrun, state->rawdev.curr_pos_w, state->rawdev.curr_pos_r, _percent_done, state->rawdev.overrun, state->rawdev.curr_pos_w, state->rawdev.curr_pos_r, _percent_done,
state->writer_params.lba_start, state->writer_params.lba_current, state->writer_params.lba_end); state->writer_params.lba_start, state->writer_params.lba_current, state->writer_params.lba_end,
_audio_en, state->audio.set_audio_channels, state->audio.set_audio_rate, state->audio.set_audio_volume);
FOR_EACH_PORT(int, chn) { FOR_EACH_PORT(int, chn) {
char *_active = is_chn_active(state, chn) ? "yes" : "no"; char *_active = is_chn_active(state, chn) ? "yes" : "no";
...@@ -1204,6 +1339,11 @@ void camogm_status(camogm_state *state, char * fn, int xml) ...@@ -1204,6 +1339,11 @@ void camogm_status(camogm_state *state, char * fn, int xml)
fprintf(f, "lba_current \t%llu\n", state->writer_params.lba_current); fprintf(f, "lba_current \t%llu\n", state->writer_params.lba_current);
fprintf(f, "lba_end \t%llu\n", state->writer_params.lba_end); fprintf(f, "lba_end \t%llu\n", state->writer_params.lba_end);
fprintf(f, "\n"); fprintf(f, "\n");
fprintf(f, "audio_enable \t%s\n", _audio_en);
fprintf(f, "audio_channels \t%d\n", state->audio.set_audio_channels);
fprintf(f, "audio_rate \t%d\n", state->audio.set_audio_rate);
fprintf(f, "audio_volume \t%d\n", state->audio.set_audio_volume);
fprintf(f, "\n");
FOR_EACH_PORT(int, chn) { FOR_EACH_PORT(int, chn) {
char *_active = is_chn_active(state, chn) ? "yes" : "no"; char *_active = is_chn_active(state, chn) ? "yes" : "no";
fprintf(f, "===== Sensor port %d status =====\n", chn); fprintf(f, "===== Sensor port %d status =====\n", chn);
...@@ -1349,17 +1489,18 @@ int parse_cmd(camogm_state *state, FILE* npipe) ...@@ -1349,17 +1489,18 @@ int parse_cmd(camogm_state *state, FILE* npipe)
// now cmd is trimmed, arg is NULL or a pointer to trimmed command arguments // now cmd is trimmed, arg is NULL or a pointer to trimmed command arguments
if (strcmp(cmd, "start") == 0) { if (strcmp(cmd, "start") == 0) {
check_compressors(state); check_compressors(state);
get_disk_info(state); if (state->rawdev_op)
camogm_start(state); get_disk_info(state);
camogm_start(state, true);
return 1; return 1;
} else if (strcmp(cmd, "reset") == 0) { // will reset pointer to the last acquired frame (if any) } else if (strcmp(cmd, "reset") == 0) { // will reset pointer to the last acquired frame (if any)
camogm_reset(state); camogm_reset(state);
return 2; return 2;
} else if (strcmp(cmd, "stop") == 0) { } else if (strcmp(cmd, "stop") == 0) {
camogm_stop(state); camogm_stop(state, true);
return 3; return 3;
} else if (strcmp(cmd, "exit") == 0) { } else if (strcmp(cmd, "exit") == 0) {
camogm_stop(state); camogm_stop(state, true);
camogm_free(state); camogm_free(state);
clean_up(state); clean_up(state);
exit(0); exit(0);
...@@ -1496,6 +1637,18 @@ int parse_cmd(camogm_state *state, FILE* npipe) ...@@ -1496,6 +1637,18 @@ int parse_cmd(camogm_state *state, FILE* npipe)
} else if (strcmp(cmd, "dummy_read") == 0) { } else if (strcmp(cmd, "dummy_read") == 0) {
if ((args) && ((d = strtol(args, NULL, 10)) > 0)) camogm_set_dummy_read(state, d); if ((args) && ((d = strtol(args, NULL, 10)) > 0)) camogm_set_dummy_read(state, d);
return 30; return 30;
} else if (strcmp(cmd, "audio") == 0) {
if (args)
camogm_set_audio_state(state, args);
return 31;
} else if (strcmp(cmd, "audio_volume") == 0) {
if (args)
camogm_set_audio_volume(state, args);
return 32;
} else if (strcmp(cmd, "audio_format") == 0) {
if (args)
camogm_set_audio_format(state, args);
return 33;
} }
return -1; return -1;
...@@ -1581,6 +1734,9 @@ int listener_loop(camogm_state *state) ...@@ -1581,6 +1734,9 @@ int listener_loop(camogm_state *state)
} else if (state->prog_state == STATE_RUNNING) { // no commands in queue, started } else if (state->prog_state == STATE_RUNNING) { // no commands in queue, started
switch ((rslt = -sendImageFrame(state))) { switch ((rslt = -sendImageFrame(state))) {
case 0: case 0:
if (state->format == CAMOGM_FORMAT_MOV) {
audio_process(&state->audio);
}
break; // frame sent OK, nothing to do (TODO: check file length/duration) break; // frame sent OK, nothing to do (TODO: check file length/duration)
case CAMOGM_FRAME_NOT_READY: // just wait for the frame to appear at the current pointer case CAMOGM_FRAME_NOT_READY: // just wait for the frame to appear at the current pointer
// we'll wait for a frame, not to waste resources. But if the compressor is stopped this program will not respond to any commands // we'll wait for a frame, not to waste resources. But if the compressor is stopped this program will not respond to any commands
...@@ -1605,8 +1761,8 @@ int listener_loop(camogm_state *state) ...@@ -1605,8 +1761,8 @@ int listener_loop(camogm_state *state)
case CAMOGM_FRAME_BROKEN: // frame broken (buffer overrun) case CAMOGM_FRAME_BROKEN: // frame broken (buffer overrun)
// restart the file // restart the file
D3(fprintf(debug_file,"%s:line %d - sendImageFrame() returned -%d\n", __FILE__, __LINE__, rslt)); D3(fprintf(debug_file,"%s:line %d - sendImageFrame() returned -%d\n", __FILE__, __LINE__, rslt));
camogm_stop(state); camogm_stop(state, false);
camogm_start(state); camogm_start(state, false);
break; break;
case CAMOGM_FRAME_FILE_ERR: // error with file I/O case CAMOGM_FRAME_FILE_ERR: // error with file I/O
case CAMOGM_FRAME_OTHER: // other errors case CAMOGM_FRAME_OTHER: // other errors
...@@ -1628,7 +1784,7 @@ int listener_loop(camogm_state *state) ...@@ -1628,7 +1784,7 @@ int listener_loop(camogm_state *state)
} else if (state->prog_state == STATE_STARTING) { // no commands in queue,starting (but not started yet) } else if (state->prog_state == STATE_STARTING) { // no commands in queue,starting (but not started yet)
// retry starting // retry starting
switch ((rslt = -camogm_start(state))) { switch ((rslt = -camogm_start(state, true))) {
case 0: case 0:
break; // file started OK, nothing to do break; // file started OK, nothing to do
case CAMOGM_TOO_EARLY: case CAMOGM_TOO_EARLY:
...@@ -2156,3 +2312,19 @@ struct timeval get_fpga_time(const int fd_fparsall, unsigned int port) ...@@ -2156,3 +2312,19 @@ struct timeval get_fpga_time(const int fd_fparsall, unsigned int port)
return tv; return tv;
} }
/**
* Wrapper for a function returning FPGA time. This wrapper is set as a callback function for
* audio interface. The time value is retrieved through currently active port.
* @param[in] audio pointer to a structure containing audio parameters and buffers
* @param[out] tv pointer to a structure where time value will be stored
* @return None
*/
static void get_fpga_time_w(const struct audio *audio, struct timeval *tv)
{
unsigned int port;
camogm_state *state = container_of(audio, camogm_state, audio);
port = state->port_num;
*tv = get_fpga_time(state->fd_fparmsall[port], port);
}
...@@ -22,10 +22,13 @@ ...@@ -22,10 +22,13 @@
#include <pthread.h> #include <pthread.h>
#include <stdbool.h> #include <stdbool.h>
#include <ogg/ogg.h> #include <ogg/ogg.h>
#include "ogmstreams.h"
#include <elphel/exifa.h> #include <elphel/exifa.h>
#include <elphel/c313a.h> #include <elphel/c313a.h>
#include <elphel/x393_devices.h> #include <elphel/x393_devices.h>
#include <sys/uio.h>
#include "ogmstreams.h"
#include "camogm_audio.h"
#define CAMOGM_FRAME_NOT_READY 1 ///< frame pointer valid, but not yet acquired #define CAMOGM_FRAME_NOT_READY 1 ///< frame pointer valid, but not yet acquired
#define CAMOGM_FRAME_INVALID 2 ///< invalid frame pointer #define CAMOGM_FRAME_INVALID 2 ///< invalid frame pointer
...@@ -256,6 +259,8 @@ typedef struct { ...@@ -256,6 +259,8 @@ typedef struct {
uint16_t sock_port; ///< command socket port number uint16_t sock_port; ///< command socket port number
struct writer_params writer_params; ///< contains control parameters for writing thread struct writer_params writer_params; ///< contains control parameters for writing thread
unsigned int error_stat[SENSOR_PORTS][CAMOGM_ERRNUM]; ///< collect statistics about errors unsigned int error_stat[SENSOR_PORTS][CAMOGM_ERRNUM]; ///< collect statistics about errors
struct audio audio; ///< various parameters related to audio
} camogm_state; } camogm_state;
extern int debug_level; extern int debug_level;
......
/**
* @file camogm_audio.c
* @brief Provides audio interface for camogm
* @copyright Copyright (C) 2017 Elphel Inc.
* @author AUTHOR <EMAIL>
*
* @par License:
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <assert.h>
#include "camogm.h"
#include "camogm_audio.h"
#include "thelper.h"
static void audio_deinit(struct audio *audio);
/**
* Initialize audio interface.
* Asserts:
* audio->get_fpga_time pointer to callback function is not set
* @param audio pointer to a structure containing audio parameters and buffers
* @param restart flag indicating that full restart is requested
* @return None
*/
void audio_init(struct audio *audio, bool restart)
{
// apply settings in case they have changed
audio->audio_enable = audio->set_audio_enable;
audio->audio_rate = audio->set_audio_rate;
audio->audio_channels = audio->set_audio_channels;
audio->audio_volume = audio->set_audio_volume;
audio->ctx_a.sample_time = SAMPLE_TIME;
if(audio->audio_enable == 0) {
return;
}
// set up audio device
if (restart) {
int err;
bool init_ok = false;
unsigned int t = audio->audio_rate;
unsigned int period_time = 40 * 1000;
unsigned int buffer_time = 2000000;
snd_pcm_hw_params_t *hw_params; // allocated on stack, do not free
snd_pcm_sw_params_t *sw_params; // allocated on stack, do not free
snd_pcm_status_t *status; // allocated on stack, do not free
snd_timestamp_t audio_ts;
audio->ctx_a.sbuffer_len = audio->audio_rate * audio->ctx_a.sample_time;
audio->ctx_a.sbuffer_len /= 1000;
audio->ctx_a.sbuffer_len -= audio->ctx_a.sbuffer_len % 2;
// 'while' loop here just to break initialization sequence after an error
while (true) {
audio->ctx_a.sbuffer = (void *)malloc(audio->ctx_a.sbuffer_len * 8 + AUDIO_SBUFFER_PREFIX);
if (audio->ctx_a.sbuffer == NULL) {
D0(fprintf(debug_file, "error: can not allocate buffer for audio samples: %s\n", strerror(errno)));
break;
}
D6(fprintf(debug_file, "audio sbuffer_len == %ld\n", audio->ctx_a.sbuffer_len));
if ((err = snd_pcm_open(&audio->ctx_a.capture_hnd, audio->dev_name, SND_PCM_STREAM_CAPTURE, 0)) < 0)
break;
snd_pcm_hw_params_alloca(&hw_params);
if ((err = snd_pcm_hw_params_any(audio->ctx_a.capture_hnd, hw_params)) < 0)
break;
if ((err = snd_pcm_hw_params_set_access(audio->ctx_a.capture_hnd, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
break;
if ((err = snd_pcm_hw_params_set_format(audio->ctx_a.capture_hnd, hw_params, SND_PCM_FORMAT_S16_LE)) < 0)
break;
if ((err = snd_pcm_hw_params_set_rate_near(audio->ctx_a.capture_hnd, hw_params, &t, 0)) < 0)
break;
audio->audio_rate = t;
if ((err = snd_pcm_hw_params_set_channels(audio->ctx_a.capture_hnd, hw_params, audio->audio_channels)) < 0)
break;
if ((err = snd_pcm_hw_params_set_period_time_near(audio->ctx_a.capture_hnd, hw_params, &period_time, 0)) < 0)
break;
if ((err = snd_pcm_hw_params_set_buffer_time_near(audio->ctx_a.capture_hnd, hw_params, &buffer_time, 0)) < 0)
break;
if ((err = snd_pcm_hw_params(audio->ctx_a.capture_hnd, hw_params)) < 0)
break;
snd_pcm_sw_params_alloca(&sw_params);
if ((err = snd_pcm_sw_params_current(audio->ctx_a.capture_hnd, sw_params)) < 0)
break;
if ((err = snd_pcm_sw_params_set_tstamp_mode(audio->ctx_a.capture_hnd, sw_params, SND_PCM_TSTAMP_ENABLE)) < 0)
break;
if ((err = snd_pcm_sw_params_set_tstamp_type(audio->ctx_a.capture_hnd, sw_params, SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY)) < 0)
break;
if ((err = snd_pcm_sw_params(audio->ctx_a.capture_hnd, sw_params)) < 0)
break;
init_ok = true;
break;
}
if (init_ok) {
snd_pcm_prepare(audio->ctx_a.capture_hnd);
snd_pcm_reset(audio->ctx_a.capture_hnd);
audio_set_volume(audio->audio_volume);
snd_pcm_readi(audio->ctx_a.capture_hnd, (void *)audio->ctx_a.sbuffer, 8);
snd_pcm_status_alloca(&status);
snd_pcm_status(audio->ctx_a.capture_hnd, status);
snd_pcm_status_get_tstamp(status, &audio_ts);
audio->ctx_a.begin_of_stream_with_audio = 1;
audio->ctx_a.audio_trigger = 1;
audio->ctx_a.audio_count = 0;
audio->ctx_a.audio_skip_samples = 0;
struct timeval fpga_tv, sys_tv;
assert(audio->get_fpga_time);
audio->get_fpga_time((const struct audio *)audio, &fpga_tv);
gettimeofday(&sys_tv, NULL);
struct timeval d; // system and FPGA time difference
d.tv_sec = sys_tv.tv_sec - 1;
d.tv_usec = sys_tv.tv_usec + 1000000;
d.tv_sec -= fpga_tv.tv_sec;
d.tv_usec -= fpga_tv.tv_usec;
time_normalize(&d);
struct timeval tv;
tv.tv_sec = audio_ts.tv_sec;
tv.tv_usec = audio_ts.tv_usec;
tv.tv_sec -= 1;
tv.tv_usec += 1000000;
tv.tv_sec -= d.tv_sec;
tv.tv_usec -= d.tv_usec;
time_normalize(&tv);
audio->ts_audio = tv;
D4(fprintf(debug_file, "audio_init OK, system time = %ld:%06ld, FPGA time = %ld:%06ld\n",
sys_tv.tv_sec, sys_tv.tv_usec, fpga_tv.tv_sec, fpga_tv.tv_usec));
} else {
audio->set_audio_enable = 0;
audio->audio_enable = 0;
D0(fprintf(debug_file, "Error: audio init failed and audio capture is disabled; ALSA error message: %s\n", snd_strerror(err)));
}
}
}
void audio_start(struct audio *audio)
{
audio->audio_frameno = 0;
audio->audio_samples = 0;
audio->ctx_a.rem_samples = 0;
audio->ctx_a.time_last.tv_sec = 0;
audio->ctx_a.time_last.tv_usec = 0;
audio->ctx_a.audio_count = 0;
}
/**
* Process audio stream.
* Asserts:
* audio->write_samples pointer to a function is not set
* @param audio pointer to a structure containing audio parameters and buffers
* @return None
*/
void audio_process(struct audio *audio)
{
int slen;
int counter = 0;
void *_buf;
int _buf_len;
struct timeval tv_sys;
snd_timestamp_t ts;
snd_pcm_status_t *status; // allocated on stack, do not free
if (audio->audio_enable == 0)
return;
snd_pcm_status_alloca(&status);
for (;;) {
long avail = 0;
int to_push_flag = 0;
counter++;
gettimeofday(&tv_sys, NULL);
tv_sys.tv_usec += audio->ctx_a.sample_time;
time_normalize(&tv_sys);
snd_pcm_status(audio->ctx_a.capture_hnd, status);
snd_pcm_status_get_tstamp(status, &ts);
avail = snd_pcm_status_get_avail(status);
int to_read = audio->ctx_a.sbuffer_len; // length in samples
if (audio->ctx_a.rem_samples < 0)
audio->ctx_a.rem_samples = 0;
if (avail >= audio->ctx_a.sbuffer_len && audio->ctx_a.rem_samples == 0)
to_push_flag = 1;
if (audio->ctx_a.rem_samples > 0) {
if (audio->ctx_a.rem_samples > audio->ctx_a.sbuffer_len) {
if (avail >= audio->ctx_a.sbuffer_len) {
to_read = audio->ctx_a.sbuffer_len;
audio->ctx_a.rem_samples -= audio->ctx_a.sbuffer_len;
to_push_flag = 2;
}
} else {
if (avail >= audio->ctx_a.rem_samples) {
to_read = audio->ctx_a.rem_samples;
audio->ctx_a.rem_samples = 0;
to_push_flag = 2;
}
}
}
if (to_push_flag) {
slen = snd_pcm_readi(audio->ctx_a.capture_hnd, (void *)(audio->ctx_a.sbuffer + AUDIO_SBUFFER_PREFIX), to_read);
if (slen > 0) {
int flag = 1;
long offset = 0;
// check the length of the movie and sound track
if (to_push_flag == 1) {
struct timeval sl = audio->ctx_a.time_last;
sl.tv_usec += audio->ctx_a.sample_time;
time_normalize(&sl);
struct timeval m_end;
m_end = audio->ts_video;
m_end.tv_usec += audio->frame_period / 2;
time_normalize(&m_end);
struct timeval m_len;
m_len.tv_sec = m_end.tv_sec - 1;
m_len.tv_usec = m_end.tv_usec + 1000000;
m_len.tv_sec -= audio->ctx_a.time_start.tv_sec;
m_len.tv_usec -= audio->ctx_a.time_start.tv_usec;
time_normalize(&m_len);
if (time_comp(&sl, &m_len) > 0) {
// sound too early - skip this sequence
break;
}
}
// we need to skip some samples in a new session, but if we just switch the frames than
// we need to split new samples in the buffer into two parts - for the previous file,
// and the next one...
// so we can just save in the first file new data, and in the next use "skip_samples" field
if (audio->ctx_a.audio_skip_samples != 0) {
D5(fprintf(debug_file, "skip_samples = %lld, available samples = %d\n", audio->ctx_a.audio_skip_samples, slen));
if (audio->ctx_a.audio_skip_samples >= slen) {
audio->ctx_a.audio_skip_samples -= slen;
flag = 0;
} else {
offset = audio->ctx_a.audio_skip_samples;
audio->ctx_a.audio_skip_samples = 0;
}
}
if (flag) {
long samples = slen - offset;
audio->ctx_a.audio_count += samples;
_buf = (void *) (audio->ctx_a.sbuffer + AUDIO_SBUFFER_PREFIX);
_buf = (void *) ((char *) _buf + offset * 2 * audio->audio_channels);
_buf_len = samples * 2 * audio->audio_channels;
assert(audio->write_samples);
audio->write_samples(_buf, _buf_len, samples);
float tr = 1.0 / audio->audio_rate;
float l = tr * audio->ctx_a.audio_count;
unsigned long s = (unsigned long) l;
l -= s;
l *= 1000000;
unsigned long us = (unsigned long) l;
audio->ctx_a.time_last.tv_sec = s;
audio->ctx_a.time_last.tv_usec = us;
D6(fprintf(debug_file, "%d: sound time %lu:%06lu, at %ld:%06ld; samples: %ld\n",
counter, s, us, tv_sys.tv_sec, tv_sys.tv_usec, samples));
}
}
} else {
break;
}
}
}
/**
* Finalize audio stream and stop hardware.
* @param audio pointer to a structure containing audio parameters and buffers
* @param reset flag indicating that HW should be reset as well
* @return None
*/
void audio_finish(struct audio *audio, bool reset)
{
struct timeval m_end;
D6(fprintf(debug_file, "movie start at: %ld:%06ld\n", audio->ctx_a.time_start.tv_sec, audio->ctx_a.time_start.tv_usec));
m_end = audio->ts_video;
m_end.tv_usec += audio->frame_period / 2;
time_normalize(&m_end);
D6(fprintf(debug_file, "movie end at: %ld:%06ld\n", m_end.tv_sec, m_end.tv_usec));
struct timeval m_len;
m_len.tv_sec = m_end.tv_sec - 1;
m_len.tv_usec = m_end.tv_usec + 1000000;
m_len.tv_sec -= audio->ctx_a.time_start.tv_sec;
m_len.tv_usec -= audio->ctx_a.time_start.tv_usec;
time_normalize(&m_len);
D6(fprintf(debug_file, "movie length: %ld:%06ld\n", m_len.tv_sec, m_len.tv_usec));
audio->ctx_a.time_start = m_end;
// calculate how many samples we need to save now for the end
struct timeval fpga_tv, sys_tv, audio_last;
audio->get_fpga_time((const struct audio *)audio, &fpga_tv);
gettimeofday(&sys_tv, NULL);
D6(fprintf(debug_file, "_________________ END ____________________\n"););
D6(fprintf(debug_file, " sys time == %ld:%06ld\n", sys_tv.tv_sec, sys_tv.tv_usec));
D6(fprintf(debug_file, " FPGA time == %ld:%06ld\n", fpga_tv.tv_sec, fpga_tv.tv_usec));
D6(fprintf(debug_file, "AUDIO sys time == %ld:%06ld\n", audio->ctx_a.time_last.tv_sec, audio->ctx_a.time_last.tv_usec););
audio_last = audio->ctx_a.time_last;
if (m_len.tv_sec > audio_last.tv_sec) {
m_len.tv_sec--;
m_len.tv_usec += 1000000;
}
m_len.tv_sec -= audio_last.tv_sec;
m_len.tv_usec -= audio_last.tv_usec;
time_normalize(&m_len);
long to_finish_us = m_len.tv_usec + 1000000 * m_len.tv_sec;
D6(fprintf(debug_file, "... and now we need to save audio for this time: %ld:%06ld - i.e. %06ld usecs\n", m_len.tv_sec, m_len.tv_usec, to_finish_us));
double s = audio->audio_rate;
s /= 1000.0;
s *= to_finish_us;
s /= 1000.0;
audio->ctx_a.rem_samples = (long) s;
// from the state->tv_video_start to ctx_a.time_last (with FPGA time recalculation)
do {
fprintf(debug_file, "process remaining samples\n");
audio_process(audio);
fprintf(debug_file, "rem_samples = %ld\n", audio->ctx_a.rem_samples);
if (audio->ctx_a.rem_samples > 0)
sched_yield();
} while (audio->ctx_a.rem_samples > 0);
if (reset)
audio_deinit(audio);
}
void audio_set_volume(int nvolume)
{
snd_mixer_t *mixer;
snd_mixer_elem_t *elem;
snd_mixer_selem_id_t *sid; // allocated on stack, do not free
snd_mixer_selem_id_alloca(&sid);
snd_mixer_open(&mixer, 0);
snd_mixer_attach(mixer, "default");
snd_mixer_selem_register(mixer, NULL, NULL);
snd_mixer_load(mixer);
for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem)) {
snd_mixer_selem_get_id(elem, sid);
if (!snd_mixer_selem_is_active(elem))
continue;
// set volume at percents for capture elements
snd_mixer_elem_t *selem = snd_mixer_find_selem(mixer, sid);
if (selem == NULL) {
break;
}
long volume_min = 0;
long volume_max = 0;
if (snd_mixer_selem_get_capture_volume_range(selem, &volume_min, &volume_max) == 0) {
// set volume only for capture
if (nvolume > 65535)
nvolume = 65535;
if (nvolume < 0)
nvolume = 0;
long long vol_new = volume_max;
vol_new *= nvolume;
vol_new /= 65535;
long vol = 0;
snd_mixer_selem_get_capture_volume(selem, SND_MIXER_SCHN_FRONT_LEFT, &vol);
snd_mixer_selem_set_capture_volume_all(selem, vol_new);
D6(fprintf(debug_file, "element %s - OLD min vol == %ld; max vol == %ld; volume == %ld\n",
snd_mixer_selem_id_get_name(sid), volume_min, volume_max, vol));
D6(snd_mixer_selem_get_capture_volume(selem, SND_MIXER_SCHN_FRONT_LEFT, &vol));
D6(fprintf(debug_file, "element %s - NEW min vol == %ld; max vol == %ld; volume == %ld\n",
snd_mixer_selem_id_get_name(sid), volume_min, volume_max, vol));
}
}
snd_mixer_close(mixer);
}
static void audio_deinit(struct audio *audio)
{
struct timeval tv;
audio->audio_enable = 0;
snd_pcm_drop(audio->ctx_a.capture_hnd);
snd_pcm_close(audio->ctx_a.capture_hnd);
free(audio->ctx_a.sbuffer);
audio->ctx_a.sbuffer = NULL;
gettimeofday(&tv, NULL);
D4(fprintf(debug_file, "audio deinitialized at %ld:%06ld\n", tv.tv_sec, tv.tv_usec));
}
/**
* @file camogm_audio.h
* @brief Provides audio interface for camogm
* @copyright Copyright (C) 2017 Elphel Inc.
* @author AUTHOR <EMAIL>
*
* @par License:
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _CAMOGM_AUDIO_H
#define _CAMOGM_AUDIO_H
#include <sys/time.h>
#include <alsa/asoundlib.h>
#define SAMPLE_RATE 44100
#define SAMPLE_CHANNELS 2
#define SAMPLE_TIME 200
#define DEFAULT_SND_DEVICE "plughw:0,0"
#define AUDIO_SBUFFER_PREFIX 16
#define AUDIO_CHANNELS_MIN 1
#define AUDIO_CHANNELS_MAX 2
#define AUDIO_RATE_MIN 11025
#define AUDIO_RATE_MAX 44100
#define DEFAULT_AUDIO_VOLUME 0xffff
struct context_audio {
char *sbuffer; ///< buffer for audio samples
long sbuffer_len; ///< the length of samples buffer in samples
long sample_time; ///< duration of one audio sample in ms
long long audio_count; ///< total number of audio samples
struct timeval time_start; ///< start time, set only when stream starts and updated with each new file
struct timeval time_last; ///< time of last audio sample
long rem_samples; ///< remaining samples
int begin_of_stream_with_audio; ///<
int audio_trigger; ///< indicates the beginning of audio recording to make some initial set ups
long long audio_skip_samples; ///<
snd_pcm_t *capture_hnd; ///< ALSA PCM handle
};
struct audio {
int audio_enable; ///< flag indicating if audio is enabled
int audio_rate; ///< sample rate
int audio_channels; ///< number of channels
int audio_volume; ///< volume set in range [0..0xFFFF]
int set_audio_enable; ///< temporary storage for new value
int set_audio_rate; ///< temporary storage for new value
int set_audio_channels; ///< temporary storage for new value
int set_audio_volume; ///< temporary storage for new value
long audio_frameno; ///< frame number counter, used as index in frames_len array
long audio_samples; ///< samples counter
unsigned long *frames_len; ///< indexes of audio frames
int *audio_samples_to_chunk; ///< an array of chunks, contains sample count in each chunk
char *dev_name; ///< the name of audio device to use
struct context_audio ctx_a; ///< current audio context
struct timeval ts_audio; ///< time stamp when audio stream started
struct timeval ts_video; ///< time stamp of each new frame
struct timeval ts_video_start; ///< time stamp of starting video frame
int frame_period; ///< video frame period, used to calculate time stamps for audio samples
void (*get_fpga_time)(const struct audio *audio, struct timeval *tv);//< callback function which can get FPGA time
int (*write_samples)(void *buff, int len, int slen); ///< callback function which actually write data to file, this must set
///< in the camogm_init_* function when appropriate format is selected
};
void audio_init(struct audio *audio, bool restart);
void audio_start(struct audio *audio);
void audio_process(struct audio *audio);
void audio_finish(struct audio *audio, bool reset);
void audio_set_volume(int nvolume);
#endif /* _CAMOGM_AUDIO_H */
...@@ -66,12 +66,15 @@ int quicktime_template_parser(camogm_state *state, ...@@ -66,12 +66,15 @@ int quicktime_template_parser(camogm_state *state,
void putBigEndian(unsigned long d, int l); void putBigEndian(unsigned long d, int l);
int parse_special(void); int parse_special(void);
int parse(camogm_state *state, int top); int parse(camogm_state *state, int top);
static int camogm_audio_mov(void *buff, int len, int slen);
/** /**
* @brief Called when format is changed to MOV (only once) and recording is stopped. * @brief Called when format is changed to MOV (only once) and recording is stopped.
* Read frame template from the file if it is not done yet. * @param[in] state a pointer to a structure containing current state
* Read frame template from the file if it is not done yet and set callback function for
* audio stream recording.
*/ */
int camogm_init_mov(void) int camogm_init_mov(camogm_state *state)
{ {
FILE* qt_header; FILE* qt_header;
int size; int size;
...@@ -96,6 +99,7 @@ int camogm_init_mov(void) ...@@ -96,6 +99,7 @@ int camogm_init_mov(void)
return -CAMOGM_FRAME_FILE_ERR; return -CAMOGM_FRAME_FILE_ERR;
} }
q_template[size] = 0; q_template[size] = 0;
state->audio.write_samples = camogm_audio_mov;
return 0; return 0;
} }
...@@ -160,6 +164,22 @@ int camogm_frame_mov(camogm_state *state) ...@@ -160,6 +164,22 @@ int camogm_frame_mov(camogm_state *state)
return 0; return 0;
} }
/**
* Write audio samples to file.
* @param buff
* @param len
* @param slen
* @return
*/
static int camogm_audio_mov(void *buff, int len, int slen)
{
int ret_val = 0;
D6(fprintf(debug_file, "write audio sample, len = %d, slen = %d\n", len, slen));
return ret_val;
}
/** /**
* @brief Move to the start of the file and insert generated header * @brief Move to the start of the file and insert generated header
* @param[in] state pointer to the #camogm_state structure for current sensor port * @param[in] state pointer to the #camogm_state structure for current sensor port
......
...@@ -20,7 +20,9 @@ ...@@ -20,7 +20,9 @@
#include "camogm.h" #include "camogm.h"
int camogm_init_mov(void); #define SAMPLE_DESCR_ID_AUDIO 1 //< audio sample description ID, used in sample-to-chunk atom
int camogm_init_mov(camogm_state *state);
int camogm_start_mov(camogm_state *state); int camogm_start_mov(camogm_state *state);
int camogm_frame_mov(camogm_state *state); int camogm_frame_mov(camogm_state *state);
int camogm_end_mov(camogm_state *state); int camogm_end_mov(camogm_state *state);
......
{'moov
{'mvhd
00 # version
000000 # Flags
!time # Creation time
!time # Modification time
!timescale # Time Scale - time unints per second
!duration # Duration - in time units
00010000 # Preferred rate
0100 # Preferred volume
00000000 00000000 0000 # (reserved)
00010000 00000000 00000000 # Matrix [a b u]
00000000 00010000 00000000 # Matrix [c d v]
00000000 00000000 40000000 # Matrix [x y w]
00000000 # Preview Time
00000000 # Preview Duration
00000000 # Poster Time
00000000 # Selection Time
00000000 # Selection Duration
00000000 # Current Time
00000002 # Next Track ID
} # 'mvhd
{'trak
{'tkhd
00 # version
00000f # Flags
!time # Creation time
!time # Modification time
00000001 # Track ID
00000000 # (reserved)
!duration # Duration
00000000 00000000 # (reserved)
0000 # Layer
0000 # Alternate group
0000 # Volume
0000 # (reserved)
00010000 00000000 00000000 # Matrix [a b u]
00000000 00010000 00000000 # Matrix [c d v]
00000000 00000000 40000000 # Matrix [x y w]
!width 0000 # Track width
!height 0000 # Track height
} # 'tkhd
{'mdia
{'mdhd
00 # version
000000 # Flags
!time # Creation time
!time # Modification time
!timescale # Time Scale
!duration # Duration
0000 # Language
0000 # Quality
} # 'mdhd
{'hdlr
00 # version
000000 # Flags
'mhlr # Component type
'vide # Component subtype
'appl # Component manufacturer
00000000 # Component Flags
0001001f # Component Flags Mask
19 'Apple Video Media Handler # Component Name
} # 'hdlr
{'minf
{'vmhd
00 # version
000001 # Flags
0040 # Graphics Mode
8000 8000 8000 # Opcolor
} # 'vmhd
{'hdlr
00 # version
000000 # Flags
'dhlr # Component type
'alis # Component subtype
'appl # Cmponent manufacturer
00000001 # Component Flags
00010026 # Component Flags Mask
18 'Apple Alias Data Handler # Component Name
} # 'hdlr
{'dinf
{'dref
00 # version
000000 # Flags (1 - the same file???)
00000001 # Number of entries
0000000c 616c6973 00000001 # Data references
}
} # 'dinf
{'stbl
{'stsd
00 # version
000000 # Flags
00000001 # Number of entries
00000056 # Sample description size
'jpeg # Data format
00000000 0000 # (reserved)
0001 # Data reference index
0000 # Version
0000 # Revision Level
'appl # Vendor
00000000 # Temporal quality
00000200 # Spatial quality
!width # Width
!height # Height
00480000 # Horizontal resolution
00480000 # Vertical resolution
00000000 # Data size
0001 # Frame count
0c 'Photo - JPEG # Compressor Name
00000000 00000000 00000000 00000000 000000 # padding
0018 # Color depth (24)
ffff # Color table ID
} #'stsd
{'stts
00 # version
000000 # Flags
00000001 # Number of entries
!nframes !frame_duration # TIME-TO-SAMPLE table (was 0x0a 0x28) - time units/sample
}
{'stsc
00 # version
000000 # Flags
00000001 # Number of entries
# 00000001 !nframes 00000001 # SAMPLE-TO-CHUNK table - all in one chunk
00000001 !samples_chunk 00000001 # SAMPLE-TO-CHUNK table - each sample it new chunk
}
{'stsz
00 # version
000000 # Flags
00000000 # Sample Size ** Did not work with non-zero!
!nframes # Number of entries
!sample_sizes # nframes of 4-byte words
}
{'stco
00 # version
000000 # Flags
# 00000001 # Number of entries
# !mdata #
# !nframes # Number of entries
!chunk_offsets #
}
} #'stbl
} #'minf
} # 'mdia
# {'udta
# 00000000
# }
} #'trak
# sound track
{'trak
{'tkhd
00 # Version
00000f # Flags
!time # Creation time
!time # Modification time
00000002 # Track ID
00000000 # (reserved)
!duration # Duration (correct)
00000000 # (reserved)
00000000 # (reserved)
0000 # Layer
0000 # Alternate group
0100 # Volume
0000 # (reserved)
00010000 00000000 00000000 # Matrix [a b u]
00000000 00010000 00000000 # Matrix [c d v]
00000000 00000000 40000000 # Matrix [x y w]
00000000 # Track width
00000000 # Track height
} #'tkhd
{'mdia
{'mdhd # - required
00 # version
000000 # Flags
!time # Creation time
!time # Modification time
!audio_timescale # Time Scale - 44100 // ???
!audio_duration # Duration // ???
0000 # Language
0000 # Quality
} # 'mdhd
{'hdlr # - not required
00 # version
000000 # Flags
'mhlr # Component type
'soun # Component subtype
00000000 # Component manufacturer
# 'niko # Component manufacturer
00000000 # Component Flags
00000000 # Component Flags Mask
00 # Component name
# 'appl # Component manufacturer
# 00000001 # Component Flags
# 00010026 # Component Flags Mask
# 18 'Apple Sound Data Handler # Component Name
} # 'hdlr
{'minf
{'smhd
00 # version
000000 # Flags
0000 # Balance
0000 # Reserved
} # 'smhd
{'hdlr
00 # version
000000 # Flags
'dhlr # Component type
'alis # Component subtype
00000000 # Component manufacturer
# 'niko # Component manufacturer
00000000 # Component Flags
00000000 # Component Flags Mask
00 #
# 00000001 # Component Flags
# 00010026 # Component Flags Mask
#18 'Apple Alias Data Handler # Component Name
} # 'hdlr
{'dinf
{'dref
00 # version
000000 # Flags (1 - the same file???)
00000001 # Number of entries
{'alis
00000001
}
}
} # 'dinf
{'stbl # Sample Table atom
{'stsd # Sample Description atom
00 # Version
000000 # Flags
00000001 # Number of entries
{'sowt # 16 bit LE, Data format
# 0001 # Version
# 0000 # Revision
00000000 0000 # (reserved)
0001 # Data reference index
0001 # Version
0000 # Revision level
00000000 # Vendor
!audio_channels # 2b, Number of channels
0010 # Sample size, == 0x10 == 16 bit
0000 # Compression ID
0000 # Packet size
!audio_rate # 4b, Sample rate - unsigned 16.16 FP
# new fields...
00000001 # samples per packets - always 1
00000002 # number of bytes in a sample for a single channel
!audio_bytes_per_frame # bytes per frame - 2 * channels
00000002 # bytes per sample - 2
# 00000000 # Terminator
# 00000000
}
} # 'stsd
{'stts # Time-to-sample atom - from the "Some Useful Examples: Creating Audio Tracks at 44.1 kHz"
00 # version
000000 # Flags
# !audio_stts #
00000001 # Number of entries
!audio_samples # Samples count
00000001 # Sample duration
}
{'stsc # Sample-to-chunk atom - OK
00 # version
000000 # Flags
!audio_stsc #
# 00000000 # Number of entries
#[ 00000000 # first chunk
#... 00000000 # samples per chunk
#] 00000000 # samples description ID - 01 or 02 ?
}
{'stsz # Sample size atom
00 # version
000000 # Flags
!audio_stsz # Sample Size - 2 for Mono, 4 for Stereo - ?
# 00000000 # Number of entries - empty
}
{'stco # Chunk offset atom
00 # version
000000 # Flags
!audio_stco #
}
} # 'stbl
} # 'minf
} #'mdia
} #'trak
# {'udta
# {'WLOC
# 0017
# 0020
# }
# 00000000 # ???
# }
}
!data_size
'mdat
/**
* @file thelper.c
* @brief Various helper functions to work with timeval structures
* @copyright Copyright (C) 2017 Elphel Inc.
* @author AUTHOR <EMAIL>
*
* @par License:
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "thelper.h"
/**
* Make sure that time represented by timeval structure has correct format, i.e.
* the value of microseconds does not exceed 1000000.
* @param tv time values to normalize
* @return None
*/
void time_normalize(struct timeval *tv)
{
tv->tv_sec += tv->tv_usec / 1000000;
tv->tv_usec = tv->tv_usec % 1000000;
}
/**
* Compare two times represented by timeval structure
* @param t1 first time value
* @param t2 second time value
* @return 1 if t1 > t2, 0 in case values are equal and -1 if t1 < t2
*/
int time_comp(struct timeval *t1, struct timeval *t2)
{
if (t1->tv_sec > t2->tv_sec)
return 1;
if (t1->tv_sec == t2->tv_sec) {
if (t1->tv_usec > t2->tv_usec)
return 1;
if (t1->tv_usec == t2->tv_usec)
return 0;
}
return -1;
}
/**
* @file thelper.h
* @brief Various helper functions to work with timeval structures
* @copyright Copyright (C) 2017 Elphel Inc.
* @author AUTHOR <EMAIL>
*
* @par License:
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _THELPER_H
#define _THELPER_H
#include <sys/time.h>
void time_normalize(struct timeval *tv);
int time_comp(struct timeval *t1, struct timeval *t2);
#endif /* _THELPER_H */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment