Audio/video recording. Help page has audio docs.

Version 4.0: add audio recording to videos and audio streaming to a browser.
This commit is contained in:
Bill Wilson 2017-04-11 09:49:16 -05:00
parent 611df035ad
commit c9f162d20c
32 changed files with 1881 additions and 245 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
scripts
www/.htpasswd
www/FIFO
www/audio_FIFO
www/media
www/config-user*
www/images/bg_*

View File

@ -1,8 +1,7 @@
# PiKrellCam
PiKrellCam is a video recording motion detect program with an OSD
web interface that detects motion using the Raspberry Pi camera
MMAL motion vectors.
PiKrellCam is an audio/video recording motion detect program with an OSD web
interface that detects motion using the Raspberry Pi camera MMAL motion vectors.
Read about it and install instructions at:
[PiKrellCam webpage](http://billw2.github.io/pikrellcam/pikrellcam.html)

View File

@ -111,7 +111,9 @@ echo "Starting PiKrellCam install..."
# =============== apt install needed packages ===============
#
PACKAGE_LIST=""
for PACKAGE in gpac php5 php5-common php5-fpm nginx libav-tools bc sshpass mpack imagemagick apache2-utils
for PACKAGE in gpac php5 php5-common php5-fpm nginx libav-tools bc \
sshpass mpack imagemagick apache2-utils libasound2 libasound2-dev \
libmp3lame0 libmp3lame-dev
do
if ! dpkg -s $PACKAGE 2>/dev/null | grep Status | grep -q installed
then
@ -124,7 +126,7 @@ then
echo "Installing packages: $PACKAGE_LIST"
echo "Running: apt-get update"
sudo apt-get update
sudo apt-get install -y $PACKAGE_LIST
sudo apt-get install -y --no-install-recommends $PACKAGE_LIST
else
echo "No packages need to be installed."
fi
@ -140,7 +142,7 @@ then
if ! dpkg -s realpath 2>/dev/null | grep Status | grep -q installed
then
echo "Installing package: realpath"
sudo apt-get install -y realpath
sudo apt-get install -y --no-install-recommends realpath
fi
fi

Binary file not shown.

View File

@ -72,4 +72,12 @@ else
fi
fi
MESSAGE=`$INSTALL_DIR/scripts-dist/_upgrade-message`
len=${#MESSAGE}
if (( len > 0 ))
then
echo "inform \"$MESSAGE\" 8 4 1" > $FIFO
TIMEOUT=20
fi
echo "inform timeout $TIMEOUT" > $FIFO

8
scripts-dist/_upgrade-message Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
if ! dpkg -s libmp3lame0 2>/dev/null | grep Status | grep -q installed
then
echo "WARNING: install libmp3lme0 or restart will fail!"
fi
exit 0

View File

@ -32,12 +32,15 @@ THUMB_JPEG=$5
# OR, to email the smaller motion area thumb jpg, uncomment this line:
# EMAIL_JPEG=$THUMB_JPEG
# Edit MY_EMAIL to your email address to email the preview jpeg.
# Edit MY_EMAIL to your email address to email the preview jpeg and uncomment
# the mpack line. Run mpack in the background because if it is slow in
# sending the email, pikrellcam is waiting for this script to end and the
# preview display can pause.
#
MY_EMAIL=myuser@gmail.com
#mpack -s pikrellcam@$HOSTNAME $EMAIL_JPEG $MY_EMAIL
#mpack -s pikrellcam@$HOSTNAME $EMAIL_JPEG $MY_EMAIL &
#echo "mpack -s pikrellcam@$HOSTNAME $PREVIEW_JPEG $MY_EMAIL" >> $LOG_FILE
#echo "mpack -s pikrellcam@$HOSTNAME $PREVIEW_JPEG $MY_EMAIL &" >> $LOG_FILE

View File

@ -21,10 +21,11 @@ MMAL_LIB ?= -L/opt/vc/lib -lbcm_host -lvcos -lmmal -lmmal_core -lmmal_util \
FLAGS = -O2 -Wall $(MMAL_INCLUDE) $(INCLUDES)
LIBS = $(MMAL_LIB) -lm -lpthread
LIBS = $(MMAL_LIB) -lm -lpthread -lasound -lmp3lame
LOCAL_SRC = pikrellcam.c mmalcam.c motion.c event.c display.c config.c servo.c \
preset.c sunriset.c multicast.c tcpserver.c tcpserver.c tcpserver_mjpeg.c
LOCAL_SRC = pikrellcam.c mmalcam.c motion.c event.c display.c config.c \
servo.c preset.c sunriset.c multicast.c audio.c \
tcpserver.c tcpserver.c tcpserver_mjpeg.c
KRELLMLIB_SRC = $(wildcard $(addsuffix /*.c,$(LIBKRELLM_DIRS)))
SOURCES = $(LOCAL_SRC) $(KRELLMLIB_SRC)

927
src/audio.c Normal file
View File

@ -0,0 +1,927 @@
/* PiKrellCam
|
| Copyright (C) 2015-2017 Bill Wilson billw@gkrellm.net
|
| PiKrellCam 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.
|
| PiKrellCam 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/
|
| This file is part of PiKrellCam.
*/
#include "pikrellcam.h"
AudioCircularBuffer audio_circular_buffer;
static pthread_t audio_thread_ref;
static int thread_ret = 1;
static float gain;
#define FORMAT_CODE_PCM 1
#define N_PERIODS 8
typedef struct
{
char RIFF[4];
uint32_t file_size;
char type[4];
char format_chunk_id[4];
uint32_t format_chunk_size;
uint16_t format_code;
uint16_t n_channels;
uint32_t sample_rate;
uint32_t byte_rate;
uint16_t bytes_per_sample;
uint16_t bits_per_sample;
char data[4];
uint32_t data_size;
} WaveHeader;
static WaveHeader wave_header;
static FILE *debug_wave_file;
static char *debug_wave_path = "/tmp/debug.wav";
static int max_mp3_encode_periods;
/* Video buffer is initialized first, so vcb->seconds is right.
| Usually called after video_circular_buffer_init() while vcb is locked,
| but also called from audio_mic_open() before audio_thread is started.
*/
void
audio_circular_buffer_init(void)
{
VideoCircularBuffer *vcb = &video_circular_buffer;
AudioCircularBuffer *acb = &audio_circular_buffer;
int size;
if (!acb->pcm)
return;
acb->head = acb->tail = 0;
acb->channels = pikrellcam.audio_channels;
acb->n_frames = acb->rate * vcb->seconds;
size = SND_FRAMES_TO_BYTES(acb, acb->n_frames);
if (acb->data_size != size)
{
if (acb->data)
free(acb->data);
acb->data = (int16_t *) malloc(size);
if (acb->data)
{
acb->data_size = size;
// memset(acb->data, 0, acb->data_size);
log_printf("audio circular buffer - %.2f KB %.2f KFrames (%d sec, %d samples/sec, %d channels)\n",
(float) size / 1000.0, (float) acb->n_frames / 1000.0,
vcb->seconds, acb->rate, pikrellcam.audio_channels);
}
else
{
acb->data_size = 0;
acb->n_frames = 0;
log_printf("Audio circular buffer malloc() failed: %m\n");
}
}
}
static void
audio_record_write(AudioCircularBuffer *acb, void *buf, int n_frames)
{
int n;
if (!acb->mp3_file)
return;
if (acb->channels == 1)
n = lame_encode_buffer(acb->lame_record, buf, NULL, n_frames,
acb->mp3_record_buffer, acb->mp3_record_buffer_size);
else
n = lame_encode_buffer_interleaved(acb->lame_record, buf, n_frames,
acb->mp3_record_buffer, acb->mp3_record_buffer_size);
if (n > 0)
fwrite(acb->mp3_record_buffer, n, 1, acb->mp3_file);
if ((pikrellcam.audio_debug & 0x1) && debug_wave_file)
fwrite(buf, SND_FRAMES_TO_BYTES(acb, n_frames), 1, debug_wave_file);
}
/* When set head/tail functions are called from h264 encoder callback,
| vcb mutex is locked.
*/
void
audio_buffer_set_record_head(AudioCircularBuffer *acb, int head)
{
VideoCircularBuffer *vcb = &video_circular_buffer;
if (!acb->pcm || !acb->lame_record)
return;
acb->record_head = head; /* don't need mutex */
if (pikrellcam.audio_debug & 0x4)
{
printf("%.2f audio_set_record_head:%d (tail:%d frames:%d) %.2f video (frames:%d)\n",
(float) acb->frame_count / (float) acb->rate,
head, acb->tail, acb->frame_count,
(float) (vcb->last_pts - pikrellcam.video_start_pts) / 1e6,
vcb->frame_count);
}
}
void
audio_buffer_set_tail(AudioCircularBuffer *acb, int position)
{
pthread_mutex_lock(&acb->mutex);
acb->tail = position;
if (pikrellcam.audio_debug & 0x4)
printf("set_tail tail:%d head: %d\n", acb->tail, acb->head);
pthread_mutex_unlock(&acb->mutex);
}
/* If tail < 0, tail is delta to set behind head. Otherwise it is absolute
| tail.
*/
void
audio_buffer_set_record_head_tail(AudioCircularBuffer *acb, int head, int tail)
{
VideoCircularBuffer *vcb = &video_circular_buffer;
pthread_mutex_lock(&acb->mutex);
acb->record_head = head;
if (tail < 0)
{
acb->tail = head + tail;
if (acb->tail < 0)
acb->tail += acb->n_frames;
}
else
acb->tail = tail;
if (pikrellcam.audio_debug & 0x4)
{
printf("%.2f audio_set_record_head:%d_tail:%d (tail:%d frames:%d) %.2f video (frames:%d)\n",
(float) acb->frame_count / (float) acb->rate,
head, tail, acb->tail, acb->frame_count,
(float) (vcb->last_pts - pikrellcam.video_start_pts) / 1e6,
vcb->frame_count);
}
pthread_mutex_unlock(&acb->mutex);
}
int
audio_frames_offset_from_video(AudioCircularBuffer *acb)
{
VideoCircularBuffer *vcb = &video_circular_buffer;
double vid_time;
int pending, offset;
if (vcb->video_frame_count < 2)
return 0;
vid_time = (double)(vcb->last_pts - pikrellcam.video_start_pts) / 1e6;
vid_time /= vcb->video_frame_count - 1;
vid_time *= vcb->video_frame_count;
pthread_mutex_lock(&acb->mutex);
pending = acb->record_head - acb->tail;
if (pending < 0)
pending += acb->n_frames;
offset = pending + acb->frame_count
- (int) (vid_time * (double) acb->rate);
if (pikrellcam.audio_debug & 0x4)
{
printf(" rhead:%d tail:%d pending:%d frames:%d vid_time_usec:%d\n",
acb->record_head, acb->tail, pending, acb->n_frames,
(int) (vid_time * 1e6));
}
pthread_mutex_unlock(&acb->mutex);
return offset;
}
static void
wave_header_init(WaveHeader *header, uint32_t sample_rate, uint16_t bit_depth,
uint16_t n_channels)
{
if (!header)
return;
memcpy(&header->RIFF, "RIFF", 4);
header->file_size = 0xefffffff;
memcpy(&header->type, "WAVE", 4);
memcpy(&header->format_chunk_id, "fmt ", 4);
header->format_chunk_size = 16; /* Size of following format data */
header->format_code = FORMAT_CODE_PCM;
header->n_channels = n_channels;
header->sample_rate = sample_rate; /* data blocks / sec */
header->byte_rate = sample_rate * n_channels * bit_depth / 8;
header->bytes_per_sample = n_channels * bit_depth / 8;
header->bits_per_sample = bit_depth;
memcpy(&header->data, "data", 4);
header->data_size = header->file_size - sizeof(WaveHeader);
}
static int
audio_gain(AudioCircularBuffer *acb, int16_t *pcm_buf, int frames)
{
int i, peak, pcm, n_pcm;
n_pcm = frames * acb->channels;
for (i = 0, peak = 0; i < n_pcm; ++i)
{
pcm = (int) ((float) pcm_buf[i] * gain);
if (pcm > INT16_MAX)
pcm = INT16_MAX;
else if (pcm < INT16_MIN)
pcm = INT16_MIN;
pcm_buf[i] = (int16_t) pcm;
if (pcm < 0)
pcm = -pcm;
if (pcm > peak)
peak = pcm;
}
return peak;
}
static void
audio_record_files_close(void)
{
AudioCircularBuffer *acb = &audio_circular_buffer;
int n = 0;
pthread_mutex_lock(&acb->mutex);
if (acb->lame_record)
{
n = lame_encode_flush(acb->lame_record,
acb->mp3_record_buffer, acb->mp3_record_buffer_size);
lame_close(acb->lame_record);
acb->lame_record = NULL;
}
if (acb->mp3_file)
{
if (n > 0)
fwrite(acb->mp3_record_buffer, n, 1, acb->mp3_file);
fclose(acb->mp3_file);
acb->mp3_file = NULL;
}
pthread_mutex_unlock(&acb->mutex);
if (debug_wave_file)
fclose(debug_wave_file);
debug_wave_file = NULL;
}
static void
audio_stream_close(int error)
{
AudioCircularBuffer *acb = &audio_circular_buffer;
int n;
pthread_mutex_lock(&acb->mutex);
if (acb->lame_stream)
{
n = lame_encode_flush(acb->lame_stream,
acb->mp3_stream_buffer, acb->mp3_stream_buffer_size);
lame_close(acb->lame_stream);
acb->lame_stream = NULL;
if (acb->mp3_stream_fd > 0)
{
if (n > 0 && !error)
write(acb->mp3_stream_fd, acb->mp3_stream_buffer, n);
close(acb->mp3_stream_fd);
acb->mp3_stream_fd = -1;
}
}
pthread_mutex_unlock(&acb->mutex);
if (error && pikrellcam.verbose)
log_printf("audio stream closed, error: %s\n", strerror(error));
}
static void *
audio_thread(void *ptr)
{
AudioCircularBuffer *acb = &audio_circular_buffer;
static struct timeval encode_timer;
int n, err, end_frames, t_usec, frames, rhead, peak,
n_frames, avail_record_frames, use_record_head;
int16_t *buf = acb->buffer;
while (1)
{
err = 0;
if ((frames = snd_pcm_readi(acb->pcm, buf, acb->period_frames)) < 0)
{
usleep(5000);
if (frames == -EAGAIN)
continue;
if ((err = snd_pcm_recover(acb->pcm, frames, 1)) == 0)
err = snd_pcm_start(acb->pcm);
}
if (err < 0)
{
log_printf("Audio recover failed: %s\n", snd_strerror(err));
pthread_mutex_lock(&acb->mutex);
snd_pcm_close(acb->pcm);
acb->pcm = NULL;
pthread_mutex_unlock(&acb->mutex);
audio_record_files_close();
audio_stream_close(0);
if (pikrellcam.audio_pathname)
{
unlink(pikrellcam.audio_pathname);
free(pikrellcam.audio_pathname);
}
pikrellcam.audio_pathname = NULL;
break;
}
if (frames <= 0)
continue;
if ((peak = audio_gain(acb, buf, frames)) > acb->vu_meter)
acb->vu_meter = peak;
end_frames = acb->n_frames - acb->head;
if (frames <= end_frames)
memcpy(acb->data + acb->head, buf,
SND_FRAMES_TO_BYTES(acb, frames));
else
{
memcpy(acb->data + acb->head, buf,
SND_FRAMES_TO_BYTES(acb, end_frames));
memcpy(acb->data, buf + end_frames,
SND_FRAMES_TO_BYTES(acb, frames - end_frames));
}
pthread_mutex_lock(&acb->mutex);
rhead = acb->record_head;
acb->head = (acb->head + frames) % acb->n_frames;
err = 0;
if (acb->mp3_stream_fd > 0 && acb->lame_stream)
{
if (pikrellcam.audio_debug & 0x2)
micro_elapsed_time(&encode_timer);
if (acb->channels == 1)
n = lame_encode_buffer(acb->lame_stream, buf, NULL,
frames, acb->mp3_stream_buffer, acb->mp3_stream_buffer_size);
else
n = lame_encode_buffer_interleaved(acb->lame_stream, buf,
frames, acb->mp3_stream_buffer, acb->mp3_stream_buffer_size);
if (pikrellcam.audio_debug & 0x2)
{
t_usec = micro_elapsed_time(&encode_timer);
printf(" audio thread stream frames: %d encode_usec: %d\n",
frames, t_usec);
}
if (n > 0)
err = write(acb->mp3_stream_fd, acb->mp3_stream_buffer, n);
}
/* Need to write from tail to record_head, but first write of
| pre-capture time or motion re-triggers after post-capture in
| event-gap can be for many seconds -> long MP3 convert time. So
| convert only chunks of max_record_frames and catch up in event-gap.
*/
if (acb->lame_record && acb->mp3_file && acb->tail != rhead)
{
avail_record_frames = rhead - acb->tail;
if (avail_record_frames < 0)
avail_record_frames += acb->n_frames;
if (avail_record_frames > acb->max_record_frames)
use_record_head =
(acb->tail + acb->max_record_frames) % acb->n_frames;
else
use_record_head = rhead;
if (pikrellcam.audio_debug & 0x4)
micro_elapsed_time(&encode_timer);
if (acb->tail < use_record_head)
{
n_frames = use_record_head - acb->tail;
audio_record_write(acb, acb->data + acb->tail, n_frames);
acb->frame_count += n_frames;
}
else
{
n_frames = acb->n_frames - acb->tail;
audio_record_write(acb, acb->data + acb->tail, n_frames);
if (use_record_head > 0)
audio_record_write(acb, acb->data, use_record_head);
acb->frame_count += n_frames + use_record_head;
}
acb->tail = use_record_head;
if (pikrellcam.audio_debug & 0x4)
{
t_usec = micro_elapsed_time(&encode_timer);
printf(" audio thread record frames: %d encode_usec: %d\n",
acb->frame_count, t_usec);
}
}
pthread_mutex_unlock(&acb->mutex);
if (err < 0 && errno == EPIPE)
audio_stream_close(EPIPE);
if (acb->force_thread_exit)
{
acb->force_thread_exit = FALSE;
break;
}
}
return NULL;
}
boolean
audio_record_start(void)
{
AudioCircularBuffer *acb = &audio_circular_buffer;
int quality;
if (!acb->pcm)
return FALSE;
if (debug_wave_file)
fclose(debug_wave_file);
debug_wave_file = NULL;
acb->mp3_file = fopen(pikrellcam.audio_pathname, "w");
if (!acb->mp3_file)
{
log_printf("Failed to create mp3 file: %s\n", pikrellcam.audio_pathname);
return FALSE;
}
acb->frame_count = 0;
acb->lame_record = lame_init();
lame_set_in_samplerate(acb->lame_record, acb->rate);
lame_set_num_channels(acb->lame_record, acb->channels);
quality = (pikrellcam.pi_model == 2)
? pikrellcam.audio_mp3_quality_Pi2 : pikrellcam.audio_mp3_quality_Pi1;
lame_set_quality(acb->lame_record, quality);
lame_set_VBR_q(acb->lame_record, quality);
lame_init_params(acb->lame_record);
if (pikrellcam.audio_debug & 0x1)
{
debug_wave_file = fopen(debug_wave_path, "w");
if (debug_wave_file)
fwrite(&wave_header, sizeof(WaveHeader), 1, debug_wave_file);
}
return TRUE;
}
void
audio_record_stop(void)
{
AudioCircularBuffer *acb = &audio_circular_buffer;
int t_usec, offset;
if (!acb->pcm)
{
pikrellcam.audio_last_frame_count = 0;
pikrellcam.audio_last_rate = 0;
return;
}
offset = audio_frames_offset_from_video(acb);
if (offset < 0)
{
/* Wait enough time for head to move so have space to move record_head.
*/
t_usec = (2 - offset / (int) acb->period_frames) * acb->period_usec;
usleep(t_usec);
pthread_mutex_lock(&acb->mutex);
acb->record_head -= offset;
if (acb->record_head > acb->n_frames)
acb->record_head -= acb->n_frames;
pthread_mutex_unlock(&acb->mutex);
/* And wait more so audio thread can write from tail to record_head.
*/
usleep(t_usec);
}
acb->tail = acb->record_head;
if (pikrellcam.audio_debug & 0x2)
printf("audio_record_stop: audio_frames:%d offset:%d\n",
acb->frame_count, offset);
audio_record_files_close();
pikrellcam.audio_last_frame_count = acb->frame_count;
pikrellcam.audio_last_rate = (int) ((pikrellcam.audio_last_frame_count
/ pikrellcam.video_last_time + 0.5));
}
static int
audio_error(char *msg, int err)
{
AudioCircularBuffer *acb = &audio_circular_buffer;
char buf[128], *err_msg;
if (acb->pcm)
snd_pcm_close(acb->pcm);
acb->pcm = NULL;
err_msg = (char *) snd_strerror(err);
display_inform("\"Audio error - cannot open microphone.\" 3 3 1");
snprintf(buf, sizeof(buf), "\"%s failed: %s\" 4 3 1", msg, err_msg);
display_inform(buf);
display_inform("timeout 2");
log_printf("Audio error - %s failed: %s\n", msg, snd_strerror(err));
return FALSE;
}
void
audio_retry_open(void)
{
AudioCircularBuffer *acb = &audio_circular_buffer;
static int tries;
log_printf("audio open retry: %d of 8\n", ++tries);
if (acb->pcm || audio_mic_open(TRUE) || tries > 8)
{
tries = 0;
event_remove_name("audio retry open");
}
}
boolean
audio_mic_open(boolean inform)
{
AudioCircularBuffer *acb = &audio_circular_buffer;
int t, err, dir, size;
snd_pcm_hw_params_t *params;
if (acb->pcm)
{
display_inform("\"Microphone is already open.\" 3 3 1");
display_inform("timeout 2");
return TRUE;
}
acb->format = SND_PCM_FORMAT_S16_LE;
max_mp3_encode_periods = (pikrellcam.pi_model == 1) ? 2 : 3;
if ((err = snd_pcm_open(&acb->pcm, pikrellcam.audio_device,
SND_PCM_STREAM_CAPTURE, 0)) < 0)
return audio_error("pcm open", err);
if ((err = snd_pcm_hw_params_malloc(&params)) < 0)
return audio_error("params malloc", err);
if ((err = snd_pcm_hw_params_any(acb->pcm, params)) < 0)
return audio_error("params any", err);
if ((err = snd_pcm_hw_params_set_access(acb->pcm, params,
SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
return audio_error("set access", err);
if ((err = snd_pcm_hw_params_set_format(acb->pcm, params, acb->format)) < 0)
return audio_error("set format", err);
acb->rate = (pikrellcam.pi_model == 2) ?
pikrellcam.audio_rate_Pi2 : pikrellcam.audio_rate_Pi1;
if ((err = snd_pcm_hw_params_set_rate_near(acb->pcm, params, &acb->rate, 0)) < 0)
return audio_error("set rate", err);
if (pikrellcam.pi_model == 2)
pikrellcam.audio_rate_Pi2 = acb->rate;
else
pikrellcam.audio_rate_Pi1 = acb->rate;
if ((err = snd_pcm_hw_params_set_channels(acb->pcm, params,
pikrellcam.audio_channels)) < 0)
return audio_error("set channels", err);
/* If audio period time < 1/video_fps, audio latency can be < 1 vid frame.
*/
t = 1000000 / pikrellcam.camera_adjust.video_fps / 2;
t %= 1000;
if (t < 25000)
t = 25000;
else if (t > 62000)
t = 62000;
if ((err = snd_pcm_hw_params_set_period_time(acb->pcm, params, t, 0)) < 0)
return audio_error("set period time", err);
if (snd_pcm_hw_params_set_periods(acb->pcm, params, N_PERIODS, 0) < 0)
return audio_error("set periods", err);
if ((err = snd_pcm_hw_params(acb->pcm, params)) < 0)
return audio_error("hw params", err);
if ((err = snd_pcm_hw_params_get_channels(params, &acb->channels)) < 0)
return audio_error("get channels", err);
pikrellcam.audio_channels = acb->channels;
if ((err = snd_pcm_hw_params_get_period_size(params, &acb->period_frames, &dir)) < 0)
return audio_error("get period size", err);
if ((err = snd_pcm_hw_params_get_period_time(params, &acb->period_usec, &dir)) < 0)
return audio_error("get period time", err);
if ((err = snd_pcm_hw_params_get_buffer_size(params, &acb->buffer_frames)) < 0)
return audio_error("get buffer size", err);
snd_pcm_hw_params_free(params);
audio_circular_buffer_init();
acb->mp3_file = NULL;
acb->mp3_stream_fd = -1;
size = SND_FRAMES_TO_BYTES(acb, acb->period_frames);
if (size != acb->buffer_size)
{
if (acb->buffer)
free(acb->buffer);
acb->buffer = (int16_t *) malloc(size);
acb->buffer_size = acb->buffer ? size : 0;
if (!acb->buffer)
log_printf("malloc() of period frame buffer failed: %m\n");
if (acb->mp3_record_buffer)
free(acb->mp3_record_buffer);
acb->max_record_frames = max_mp3_encode_periods * (int) acb->period_frames;
acb->mp3_record_buffer_size = acb->max_record_frames * 5 / 4 + 8000;
acb->mp3_record_buffer = (uint8_t *) malloc(acb->mp3_record_buffer_size);
if (!acb->mp3_record_buffer)
log_printf("malloc() of mp3 record buffer failed: %m\n");
if (acb->mp3_stream_buffer)
free(acb->mp3_stream_buffer);
acb->mp3_stream_buffer_size = (int) acb->period_frames * 5 / 4 + 8000;
acb->mp3_stream_buffer = (uint8_t *) malloc(acb->mp3_stream_buffer_size);
if (!acb->mp3_stream_buffer)
log_printf("malloc() of mp3 stream buffer failed: %m\n");
}
wave_header_init(&wave_header, acb->rate, 16, acb->channels);
gain = powf(10.0, pikrellcam.audio_gain_dB / 20.0);
thread_ret = pthread_create(&audio_thread_ref, NULL, audio_thread, NULL);
if (pikrellcam.audio_debug & 0x2)
printf("period_frames=%d period_buffer_size=%d rate: %d channels: %d\n",
(int) acb->period_frames, (int) acb->buffer_size,
acb->rate, acb->channels);
log_printf("mic opened: period_frames:%d period_buffer_size:%d rate:%d channels:%d MP3 quality:%d\n",
(int) acb->period_frames, (int) acb->buffer_size,
acb->rate, acb->channels,
(pikrellcam.pi_model == 2)
? pikrellcam.audio_mp3_quality_Pi2 : pikrellcam.audio_mp3_quality_Pi1);
if (inform)
{
display_inform("\"Microphone opened.\" 3 3 1");
display_inform("timeout 2");
}
return TRUE;
}
static boolean
audio_fifo_write_open(void *data)
{
AudioCircularBuffer *acb = &audio_circular_buffer;
boolean abort_if_fail = (boolean) data;
acb->mp3_stream_fd = open(pikrellcam.audio_fifo, O_WRONLY|O_NONBLOCK);
if (acb->mp3_stream_fd >= 0)
{
display_inform("\"Audio stream opened.\" 3 3 1");
display_inform("timeout 2");
return TRUE;
}
else if (abort_if_fail)
{
if (acb->lame_stream)
lame_close(acb->lame_stream);
acb->lame_stream = NULL;
display_inform("timeout 0");
display_inform("\"Failed to open audio FIFO (no reader?).\" 3 3 1");
display_inform("timeout 2");
log_printf("Failed to open audio FIFO (no reader?): %s. %m\n", pikrellcam.audio_fifo);
}
else
{
display_inform("\"Trying to open audio FIFO.\" 2 3 1");
display_inform("timeout 1");
}
return FALSE;
}
static boolean
audio_mic_close(void)
{
AudioCircularBuffer *acb = &audio_circular_buffer;
VideoCircularBuffer *vcb = &video_circular_buffer;
int i;
if (!acb->pcm)
{
display_inform("\"Microphone is already closed.\" 3 3 1");
display_inform("timeout 2");
return FALSE;
}
if (vcb->state != VCB_STATE_NONE || acb->lame_record)
{
display_inform("\"Cannot close microphone\" 3 3 1");
display_inform("\"while audio/video is recording.\" 4 3 1");
display_inform("timeout 2");
return FALSE;
}
if (thread_ret == 0)
{
thread_ret = 1;
acb->force_thread_exit = TRUE;
i = 20;
while (acb->force_thread_exit && --i > 0)
usleep(50000);
}
audio_record_files_close();
audio_stream_close(0);
if (acb->pcm)
snd_pcm_close(acb->pcm);
acb->pcm = NULL;
display_inform("\"Microphone closed.\" 3 3 1");
display_inform("timeout 2");
return TRUE;
}
#define AUDIO_MIC_OPEN 0
#define AUDIO_MIC_CLOSE 1
#define AUDIO_MIC_TOGGLE 2
#define AUDIO_GAIN 3
#define AUDIO_STREAM_OPEN 4
#define AUDIO_STREAM_CLOSE 5
typedef struct
{
char *name;
int id,
n_args;
}
AudioCommand;
static AudioCommand audio_commands[] =
{
{ "mic_open", AUDIO_MIC_OPEN, 0 },
{ "mic_close", AUDIO_MIC_CLOSE, 0 },
{ "mic_toggle", AUDIO_MIC_TOGGLE, 0 },
{ "gain", AUDIO_GAIN, 1 },
{ "stream_open", AUDIO_STREAM_OPEN, 0 },
{ "stream_close", AUDIO_STREAM_CLOSE, 0 },
};
#define N_AUDIO_COMMANDS (sizeof(audio_commands) / sizeof(AudioCommand))
void
audio_command(char *cmd_line)
{
AudioCircularBuffer *acb = &audio_circular_buffer;
AudioCommand *acmd;
char buf[64], arg1[32];
int i, n, quality, id = -1;
arg1[0] = '\0';
n = sscanf(cmd_line, "%63s %31s", buf, arg1);
if (n < 1)
return;
for (i = 0; i < N_AUDIO_COMMANDS; ++i)
{
acmd = &audio_commands[i];
if (!strcmp(acmd->name, buf))
{
if (acmd->n_args <= n - 1)
id = acmd->id;
break;
}
}
if (id == -1)
{
// inform_message("Bad audio command.");
return;
}
switch (id)
{
case AUDIO_MIC_TOGGLE:
n = pikrellcam.audio_enable;
if (acb->pcm)
{
if (audio_mic_close())
pikrellcam.audio_enable = FALSE;
}
else
pikrellcam.audio_enable = audio_mic_open(TRUE);
if (n != pikrellcam.audio_enable)
pikrellcam.config_modified = TRUE;
break;
case AUDIO_MIC_OPEN:
n = pikrellcam.audio_enable;
pikrellcam.audio_enable = audio_mic_open(TRUE);
if (n != pikrellcam.audio_enable)
pikrellcam.config_modified = TRUE;
break;
case AUDIO_MIC_CLOSE:
if (audio_mic_close())
{
if (pikrellcam.audio_enable)
pikrellcam.config_modified = TRUE;
pikrellcam.audio_enable = FALSE;
}
break;
case AUDIO_GAIN:
n = pikrellcam.audio_gain_dB;
if (!strcmp(arg1, "up"))
n += 2;
else if (!strcmp(arg1, "down"))
n -= 2;
else if (isdigit(*arg1))
n = atoi(arg1);
n &= ~1;
if (n < 0)
n = 0;
if (n > 30)
n = 30;
pikrellcam.audio_gain_dB = n;
gain = powf(10.0, pikrellcam.audio_gain_dB / 20.0);
pikrellcam.config_modified = TRUE;
break;
case AUDIO_STREAM_OPEN:
if (!acb->pcm)
{
display_inform("\"Cannot audio stream, microphone is closed.\" 3 3 1");
display_inform("timeout 2");
break;
}
if (acb->mp3_stream_fd > 0)
{
display_inform("\"Audio stream already open.\" 3 3 1");
display_inform("timeout 2");
break;
}
if (acb->lame_stream)
lame_close(acb->lame_stream);
acb->lame_stream = lame_init();
lame_set_in_samplerate(acb->lame_stream, acb->rate);
lame_set_num_channels(acb->lame_stream, acb->channels);
quality = (pikrellcam.pi_model == 2)
? pikrellcam.audio_mp3_quality_Pi2 : pikrellcam.audio_mp3_quality_Pi1;
lame_set_quality(acb->lame_stream, quality);
lame_set_VBR_q(acb->lame_stream, quality);
lame_init_params(acb->lame_stream);
/* Reader must open before write NONBLOCK open can succeed, so
| wait first and then try once more before giving up.
*/
usleep(20000);
if (!audio_fifo_write_open((void *) FALSE))
event_count_down_add("audio stream open",
12 * EVENT_LOOP_FREQUENCY,
(void *) audio_fifo_write_open, (void *) TRUE);
break;
case AUDIO_STREAM_CLOSE:
if (acb->mp3_stream_fd >= 0)
{
audio_stream_close(0);
display_inform("\"Audio stream closed.\" 3 3 1");
display_inform("timeout 2");
}
else
{
display_inform("\"Cannot close, audio stream was not open.\" 3 3 1");
display_inform("timeout 2");
}
break;
}
}

View File

@ -1,6 +1,6 @@
/* PiKrellCam
|
| Copyright (C) 2015-2016 Bill Wilson billw@gkrellm.net
| Copyright (C) 2015-2017 Bill Wilson billw@gkrellm.net
|
| PiKrellCam is free software: you can redistribute it and/or modify it
| under the terms of the GNU General Public License as published by
@ -962,6 +962,50 @@ static Config config[] =
"servo_settle_msec", "600", FALSE, {.value = &pikrellcam.servo_settle_msec }, config_value_int_set },
{ "\n# ------------------- Audio Options -----------------------\n"
"#\n"
"# Set to true to capture audio to add to videos. Web page control of\n"
"# the microphone toggle button sets this on/off\n"
"#",
"audio_enable", "false", TRUE, {.value = &pikrellcam.audio_enable }, config_value_bool_set },
{ "# ALSA hardware audio input (microphone) capture device. Using the hw:N\n"
"# limits rate values to what the hardware supports. So use the plughw:N\n"
"# plugin device to get a wider range of rates.\n"
"#",
"audio_device", "plughw:1", FALSE, {.string = &pikrellcam.audio_device }, config_string_set },
{ "# Audio rate. See Help page for info on this and remaining audio options.\n"
"# Lame suggests using only MP3 supported sample rates:\n"
"# 8000 11025 12000 16000 22050 24000 32000 44100 48000\n"
"# Audio rate for a Pi model 2 (armv7 quad core Pi2/Pi3).\n"
"#",
"audio_rate_Pi2", "48000", FALSE, {.value = &pikrellcam.audio_rate_Pi2}, config_value_int_set },
{ "# Audio rate for a Pi model 1 (armv6 single core Pi1).\n"
"#",
"audio_rate_Pi1", "24000", FALSE, {.value = &pikrellcam.audio_rate_Pi1}, config_value_int_set },
{ "# Audio channels. A USB sound card probably supports only mono and\n"
"# setting 2 channels for this case would be reverted to 1 when the\n"
"# microphone is opened.\n"
"#",
"audio_channels", "1", FALSE, {.value = &pikrellcam.audio_channels}, config_value_int_set },
{ "# Microphone audio gain dB (0 - 30). Set using the web page audio\n"
"# gain up/down control buttons.\n"
"#",
"audio_gain_dB", "0", FALSE, {.value = &pikrellcam.audio_gain_dB}, config_value_int_set },
{ "# MP3 lame encode quality for a Pi2/3, range 0 - 9.\n"
"#",
"audio_mp3_quality_Pi2", "2", FALSE, {.value = &pikrellcam.audio_mp3_quality_Pi2}, config_value_int_set },
{ "# MP3 lame encode quality for a Pi1, range 0 - 9.\n"
"#",
"audio_mp3_quality_Pi1", "7", FALSE, {.value = &pikrellcam.audio_mp3_quality_Pi1}, config_value_int_set },
{ "\n# ------------------- Miscellaneous Options -----------------------\n"
"#\n"
"# How long in seconds a notify string should stay on the stream jpeg file.\n"
@ -1128,7 +1172,7 @@ config_load(char *config_file)
if ((f = fopen(config_file, "r")) == NULL)
return FALSE;
pikrellcam.config_sequence_new = 37;
pikrellcam.config_sequence_new = 40;
while (fgets(linebuf, sizeof(linebuf), f))
{
@ -1183,6 +1227,12 @@ config_load(char *config_file)
else if (pikrellcam.annotate_text_brightness > 255)
pikrellcam.annotate_text_size = 255;
if (pikrellcam.audio_gain_dB > 30)
pikrellcam.audio_gain_dB = 30;
else if (pikrellcam.audio_gain_dB < 0)
pikrellcam.audio_gain_dB = 0;
pikrellcam.annotate_string_space_char = '_';
camera_adjust_temp = pikrellcam.camera_adjust;

View File

@ -506,6 +506,49 @@ display_preset_settings(void)
JUSTIFY_RIGHT(0), info1);
}
#define VU_HEIGHT 80
static void
display_audio(void)
{
AudioCircularBuffer *acb = &audio_circular_buffer;
DrawArea *da = &inform_area;
char buf[16];
int y0, y, dy;
static int x_stream;
if (!acb->pcm)
return;
y0 = da->height - normal_font->char_height - 1;
snprintf(buf, sizeof(buf), "%ddB", pikrellcam.audio_gain_dB);
i420_draw_string(da, normal_font, 0xff, 1, y0, buf);
dy = acb->vu_meter * VU_HEIGHT / INT16_MAX;
acb->vu_meter = 0;
y = y0 - dy - 1;
if (dy > 0)
{
glcd_draw_v_line(glcd, da, 0xc0, 3, y, dy);
glcd_draw_v_line(glcd, da, 0xff, 4, y, dy);
glcd_draw_v_line(glcd, da, 0xff, 5, y, dy);
glcd_draw_v_line(glcd, da, 0xc0, 6, y, dy);
}
glcd_draw_h_line(glcd, da, 0, 3, y, 4);
glcd_draw_rectangle(glcd, da, 0xff, 1,
y0 - (VU_HEIGHT + 2), 8, VU_HEIGHT + 2);
glcd_draw_rectangle(glcd, da, 0, 2,
y0 - (VU_HEIGHT + 1), 6, VU_HEIGHT);
if (acb->mp3_stream_fd > 0)
{
glcd_draw_h_line(glcd, da, 0xff, x_stream, da->height - 3, 5);
glcd_draw_h_line(glcd, da, 0xff, x_stream, da->height - 2, 5);
glcd_draw_h_line(glcd, da, 0xff, x_stream, da->height - 1, 5);
x_stream = (x_stream + 2) % (4 * normal_font->char_width - 5);
}
}
static void
inform_draw(void)
{
@ -1133,9 +1176,19 @@ apply_adjustment(void)
)
{
pthread_mutex_lock(&vcb->mutex);
pikrellcam.motion_times.pre_capture = motion_times_temp.pre_capture;
pikrellcam.motion_times.event_gap = motion_times_temp.event_gap;
circular_buffer_init();
if (vcb->state == VCB_STATE_NONE)
{
pikrellcam.motion_times.pre_capture = motion_times_temp.pre_capture;
pikrellcam.motion_times.event_gap = motion_times_temp.event_gap;
video_circular_buffer_init();
audio_circular_buffer_init();
}
else
{
display_inform("\"Cannot change pre_capture or event_gap\" 3 3 1");
display_inform("\"while video is recording.\" 4 3 1");
display_inform("timeout 2");
}
pthread_mutex_unlock(&vcb->mutex);
}
}
@ -1773,15 +1826,32 @@ display_inform(char *args)
{
InformLine *iline;
char str[128];
int n = 0, font = 0, row = 0, xs = 0, ys = 0, justify = 4;
int i, n = 0, font = 0, row = 0, xs = 0, ys = 0, justify = 4;
if (sscanf(args, "timeout %d\n", &n) == 1)
{
n = (n <= 0 || n > 30) ? pikrellcam.notify_duration : n;
event_count_down_add("display inform expire",
n * EVENT_LOOP_FREQUENCY, display_inform_expire, NULL);
n = (n < 0 || n > 30) ? pikrellcam.notify_duration : n;
if (n == 0)
{
event_remove_name("display inform expire");
display_inform_expire();
}
else
event_count_down_add("display inform expire",
n * EVENT_LOOP_FREQUENCY, display_inform_expire, NULL);
}
else
else if (sscanf(args, "clear %d\n", &n) == 1)
{
for (i = 0; i < N_INFORM_LINES; ++i)
{
if (inform_line[i].row == n && inform_line[i].string)
{
free(inform_line[i].string);
inform_line[i].string = NULL;
}
}
}
else if (inform_line_index < N_INFORM_LINES)
{
n = sscanf(args, "\"%127[^\"]\" %d %d %d %d %d",
str, &row, &justify, &font, &xs, &ys);
@ -1861,6 +1931,7 @@ display_draw(uint8_t *i420)
display_servo_pan();
display_servo_tilt();
}
display_audio();
display_preset_setting();
display_action = ACTION_NONE;

View File

@ -1,6 +1,6 @@
/* PiKrellCam
|
| Copyright (C) 2015-2016 Bill Wilson billw@gkrellm.net
| Copyright (C) 2015-2017 Bill Wilson billw@gkrellm.net
|
| PiKrellCam is free software: you can redistribute it and/or modify it
| under the terms of the GNU General Public License as published by
@ -436,7 +436,7 @@ event_preview_save(void)
asprintf(&pikrellcam.preview_thumb_filename, "%s/%s",
pikrellcam.thumb_dir, base);
log_printf("event preview save: copy %s -> %s\n",
log_printf(" event preview save: copy %s -> %s\n",
pikrellcam.mjpeg_filename,
pikrellcam.preview_filename);
if ((f_dst = fopen(pikrellcam.preview_filename, "w")) != NULL)
@ -542,7 +542,6 @@ state_file_write(void)
PresetSettings *settings = NULL;
char *state;
int pan, tilt;
double ftmp, fps;
if (!fname_part)
asprintf(&fname_part, "%s.part", pikrellcam.state_filename);
@ -592,24 +591,10 @@ state_file_write(void)
fprintf(f, "video_last %s\n",
pikrellcam.video_last ? pikrellcam.video_last : "none");
fprintf(f, "video_last_frame_count %d\n", pikrellcam.video_last_frame_count);
/* The pts end-start diff is from frame start of 1st frame to frame start
| of last frame so is the time of frame_count - 1 frames.
*/
if (pikrellcam.video_last_frame_count > 1)
{
ftmp = (double) (pikrellcam.video_end_pts - pikrellcam.video_start_pts) / 1e6;
ftmp /= pikrellcam.video_last_frame_count - 1;
}
else
ftmp = 0;
ftmp *= pikrellcam.video_last_frame_count;
fprintf(f, "video_last_time %.2f\n", (float) ftmp);
if (ftmp > 0)
fps = (double) pikrellcam.video_last_frame_count / ftmp;
else
fps = 0;
fprintf(f, "video_last_fps %.2f\n", (float) fps);
fprintf(f, "video_last_time %.2f\n", (float) pikrellcam.video_last_time);
fprintf(f, "video_last_fps %.2f\n", (float) pikrellcam.video_last_fps);
fprintf(f, "audio_last_frame_count %d\n", pikrellcam.audio_last_frame_count);
fprintf(f, "audio_last_rate %d\n", pikrellcam.audio_last_rate);
fprintf(f, "still_last %s\n",
pikrellcam.still_last ? pikrellcam.still_last : "none");

View File

@ -1,4 +1,4 @@
/* PiKrellCam
/* PiKrellCam
|
| Copyright (C) 2015-2016 Bill Wilson billw@gkrellm.net
|
@ -401,7 +401,7 @@ I420_video_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
void
circular_buffer_init()
video_circular_buffer_init()
{
VideoCircularBuffer *vcb = &video_circular_buffer;
int i, seconds, size;
@ -412,31 +412,34 @@ circular_buffer_init()
*/
seconds = MAX(pikrellcam.motion_times.event_gap,
pikrellcam.motion_times.pre_capture) + 5;
size = pikrellcam.camera_adjust.video_bitrate * seconds / 8;
vcb->seconds = seconds;
size = pikrellcam.camera_adjust.video_bitrate * seconds / 8;
if (size != vcb->size)
{
if (vcb->data)
free(vcb->data);
vcb->data = (int8_t *)malloc(size);
log_printf("circular buffer allocate: %.2f MBytes (%d seconds at %.1f Mbits/sec)\n",
vcb->data = (int8_t *) malloc(size);
log_printf("video circular buffer - %.2f MB (%d seconds, %.1f Mbits/sec)\n",
(float) size / 1000000.0, seconds,
(double)pikrellcam.camera_adjust.video_bitrate / 1000000.0);
}
vcb->size = size;
vcb->head = vcb->tail = 0;
if (!vcb->data)
{
log_printf("Aborting because circular buffer malloc() failed.\n");
log_printf("Aborting because video circular buffer malloc() failed.\n");
exit(1);
}
vcb->size = size;
vcb->head = 0;
vcb->cur_frame_index = 0;
vcb->pre_frame_index = 0;
vcb->in_keyframe = FALSE;
for (i = 0; i < KEYFRAME_SIZE; ++i)
{
vcb->key_frame[i].position = 0;
vcb->key_frame[i].audio_position = 0;
vcb->key_frame[i].t_frame = 0;
vcb->key_frame[i].frame_count = 0;
}
@ -444,8 +447,8 @@ circular_buffer_init()
/* Write circular buffer data from the tail to head and upate the tail.
*/
void
vcb_video_write(VideoCircularBuffer *vcb)
static void
video_buffer_write(VideoCircularBuffer *vcb)
{
if (!vcb || !vcb->file)
return;
@ -487,12 +490,12 @@ void
video_h264_encoder_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *mmalbuf)
{
VideoCircularBuffer *vcb = &video_circular_buffer;
AudioCircularBuffer *acb = &audio_circular_buffer;
MotionFrame *mf = &motion_frame;
KeyFrame *kf;
int i, end_space, t_elapsed, event = 0;
int i, end_space, t_elapsed, audio_head, event = 0;
int t_usec, dt_frame;
boolean force_stop;
time_t t_cur = pikrellcam.t_now;
static int fps_count, pause_frame_count_adjust;
static time_t t_sec, t_prev;
uint64_t t64_now;
@ -510,6 +513,9 @@ video_h264_encoder_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *mmalbuf)
return;
}
vcb->t_cur = pikrellcam.t_now;
audio_head = acb->head;
if (mmalbuf->pts > 0)
{
if (pikrellcam.t_now > tv.tv_sec + 10)
@ -549,13 +555,13 @@ video_h264_encoder_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *mmalbuf)
MMAL_PARAMETER_VIDEO_REQUEST_I_FRAME, 1);
}
if ( (mmalbuf->flags & MMAL_BUFFER_HEADER_FLAG_KEYFRAME)
&& t_cur < t_sec
&& vcb->t_cur < t_sec
)
t_cur = t_sec;
vcb->t_cur = t_sec;
}
pthread_mutex_lock(&vcb->mutex);
if (mmalbuf->flags & MMAL_BUFFER_HEADER_FLAG_CONFIG)
h264_header_save(mmalbuf);
else if (mmalbuf->flags & MMAL_BUFFER_HEADER_FLAG_CODECSIDEINFO)
@ -585,18 +591,21 @@ video_h264_encoder_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *mmalbuf)
vcb->in_keyframe = TRUE;
vcb->cur_frame_index = (vcb->cur_frame_index + 1) % KEYFRAME_SIZE;
kf = &vcb->key_frame[vcb->cur_frame_index];
kf->position = vcb->head;
kf->audio_position = audio_head;
kf->frame_count = 0;
pause_frame_count_adjust = 0;
if (vcb->pause && vcb->state == VCB_STATE_MANUAL_RECORD)
{
vcb->tail = vcb->head;
audio_buffer_set_record_head_tail(acb, audio_head, audio_head);
pts_prev = mmalbuf->pts;
pause_frame_count_adjust = 0;
}
kf->t_frame = t_cur;
kf->t_frame = vcb->t_cur;
kf->frame_pts = mmalbuf->pts;
while (t_cur - vcb->key_frame[vcb->pre_frame_index].t_frame
while (vcb->t_cur - vcb->key_frame[vcb->pre_frame_index].t_frame
> pikrellcam.motion_times.pre_capture)
{
vcb->pre_frame_index = (vcb->pre_frame_index + 1) % KEYFRAME_SIZE;
@ -626,11 +635,11 @@ video_h264_encoder_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *mmalbuf)
}
}
if (t_cur > t_prev)
if (vcb->t_cur > t_prev)
{
if (!vcb->pause)
++vcb->record_elapsed_time;
t_prev = t_cur;
t_prev = vcb->t_cur;
}
if (vcb->state == VCB_STATE_MOTION_RECORD_START)
@ -644,20 +653,27 @@ video_h264_encoder_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *mmalbuf)
pikrellcam.video_header_size = vcb->h264_header_position;
pikrellcam.video_size = vcb->h264_header_position;
vcb->tail = vcb->key_frame[vcb->record_start_frame_index].position;
vcb_video_write(vcb);
vcb->frame_count = vcb->key_frame[vcb->record_start_frame_index].frame_count;
kf = &vcb->key_frame[vcb->record_start_frame_index];
vcb->tail = kf->position;
video_buffer_write(vcb);
vcb->frame_count = kf->frame_count;
vcb->video_frame_count = vcb->frame_count;
motion_event_write(vcb, mf);
vcb->state = VCB_STATE_MOTION_RECORD;
if (pikrellcam.audio_debug & 0x4)
printf("video pre-capture frames:%d\n", vcb->video_frame_count);
audio_buffer_set_record_head_tail(acb, audio_head, kf->audio_position);
if (mf->external_trigger_time_limit > 0)
{
vcb->motion_sync_time = t_cur + mf->external_trigger_time_limit;
vcb->motion_sync_time = vcb->t_cur + mf->external_trigger_time_limit;
vcb->max_record_time = mf->external_trigger_time_limit;
}
else
{
vcb->motion_sync_time = t_cur + pikrellcam.motion_times.post_capture;
vcb->motion_sync_time = vcb->t_cur + pikrellcam.motion_times.post_capture;
vcb->max_record_time = pikrellcam.motion_record_time_limit;
}
@ -675,12 +691,17 @@ video_h264_encoder_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *mmalbuf)
pikrellcam.video_header_size = vcb->h264_header_position;
pikrellcam.video_size = vcb->h264_header_position;
vcb->tail = vcb->key_frame[vcb->record_start_frame_index].position;
vcb_video_write(vcb);
vcb->frame_count = vcb->key_frame[vcb->record_start_frame_index].frame_count;
pts_prev = 0;
kf = &vcb->key_frame[vcb->record_start_frame_index];
vcb->tail = kf->position;
video_buffer_write(vcb);
vcb->frame_count = kf->frame_count;
vcb->state = VCB_STATE_MANUAL_RECORD;
if (pikrellcam.audio_debug & 0x4)
printf("video pre-capture frames:%d\n", vcb->video_frame_count);
audio_buffer_set_record_head_tail(acb, audio_head, kf->audio_position);
pts_prev = 0;
event |= EVENT_PREVIEW_SAVE;
}
@ -741,14 +762,31 @@ video_h264_encoder_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *mmalbuf)
vcb->last_pts = mmalbuf->pts;
pts_prev = mmalbuf->pts;
}
i = 0;
if (prev_pause)
{
vcb->frame_count += pause_frame_count_adjust;
i = audio_frames_offset_from_video(acb);
if (pikrellcam.audio_debug & 0x4)
printf("Pause stop - frame_count_adjust:%d frame_count:%d i:%d time_usec:%d\n",
pause_frame_count_adjust, vcb->frame_count, i,
(int)(vcb->last_pts - pikrellcam.video_start_pts));
pause_frame_count_adjust = 0;
}
vcb->video_frame_count = vcb->frame_count;
vcb_video_write(vcb); /* Continuously write video data */
if (i >= 0) /* No adjust if sound leads video */
audio_buffer_set_record_head(acb, audio_head);
else
audio_buffer_set_record_head_tail(acb, audio_head, i);
video_buffer_write(vcb); /* Continuously write video data */
}
else if ((pikrellcam.audio_debug & 0x4) && !prev_pause)
printf("Pause start - frame_count:%d pause_frame_count:%d time_usec:%d\n",
vcb->frame_count, pause_frame_count_adjust,
(int)(vcb->last_pts - pikrellcam.video_start_pts));
prev_pause = vcb->pause;
if (force_stop)
@ -765,16 +803,17 @@ video_h264_encoder_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *mmalbuf)
| reached and we stop recording with the post_capture time
| already written.
*/
if (t_cur <= vcb->motion_sync_time)
if (vcb->t_cur <= vcb->motion_sync_time)
{
if (mmalbuf->pts > 0)
vcb->last_pts = mmalbuf->pts;
vcb->video_frame_count = vcb->frame_count;
vcb_video_write(vcb);
video_buffer_write(vcb);
audio_buffer_set_record_head(acb, audio_head);
}
if ( force_stop
|| ( mf->external_trigger_time_limit == 0
&& t_cur >= vcb->motion_last_detect_time + pikrellcam.motion_times.event_gap
&& vcb->t_cur >= vcb->motion_last_detect_time + pikrellcam.motion_times.event_gap
)
)
{

View File

@ -1313,24 +1313,43 @@ motion_command(char *cmd_line)
case PRE_CAPTURE:
pthread_mutex_lock(&vcb->mutex);
n = atoi(arg1);
pikrellcam.motion_times.pre_capture = n;
pthread_mutex_lock(&vcb->mutex);
motion_times_temp.pre_capture = n;
circular_buffer_init();
if (vcb->state == VCB_STATE_NONE)
{
n = atoi(arg1);
pikrellcam.motion_times.pre_capture = n;
motion_times_temp.pre_capture = n;
video_circular_buffer_init();
audio_circular_buffer_init();
pikrellcam.config_modified = TRUE;
}
else
{
display_inform("\"Cannot change pre_capture or event_gap\" 3 3 1");
display_inform("\"while video is recording.\" 4 3 1");
display_inform("timeout 2");
}
pthread_mutex_unlock(&vcb->mutex);
pikrellcam.config_modified = TRUE;
log_printf("command process: motion %s\n", cmd_line);
break;
case EVENT_GAP:
n = atoi(arg1);
pikrellcam.motion_times.event_gap = n;
pthread_mutex_lock(&vcb->mutex);
motion_times_temp.event_gap = n;
circular_buffer_init();
if (vcb->state == VCB_STATE_NONE)
{
n = atoi(arg1);
pikrellcam.motion_times.event_gap = n;
motion_times_temp.event_gap = n;
video_circular_buffer_init();
audio_circular_buffer_init();
pikrellcam.config_modified = TRUE;
}
else
{
display_inform("\"Cannot change pre_capture or event_gap\" 3 3 1");
display_inform("\"while video is recording.\" 4 3 1");
display_inform("timeout 2");
}
pthread_mutex_unlock(&vcb->mutex);
pikrellcam.config_modified = TRUE;
log_printf("command process: motion %s\n", cmd_line);
break;

View File

@ -1,6 +1,6 @@
/* PiKrellCam
|
| Copyright (C) 2015-2016 Bill Wilson billw@gkrellm.net
| Copyright (C) 2015-2017 Bill Wilson billw@gkrellm.net
|
| PiKrellCam is free software: you can redistribute it and/or modify it
| under the terms of the GNU General Public License as published by
@ -193,7 +193,8 @@ camera_start(void)
char *cmd;
motion_init();
circular_buffer_init();
video_circular_buffer_init();
audio_circular_buffer_init();
if (!camera_create())
{
@ -448,7 +449,6 @@ void
video_record_start(VideoCircularBuffer *vcb, int start_state)
{
MotionFrame *mf = &motion_frame;
time_t t_cur = pikrellcam.t_now;
int n;
char *s, *path, *stats_path = NULL, seq_buf[12];
boolean do_stats = FALSE;
@ -463,7 +463,7 @@ video_record_start(VideoCircularBuffer *vcb, int start_state)
n = vcb->cur_frame_index;
if (mf->external_trigger_pre_capture > vcb->seconds - 1)
mf->external_trigger_pre_capture = vcb->seconds - 1;
while (t_cur - vcb->key_frame[n].t_frame < mf->external_trigger_pre_capture)
while (vcb->t_cur - vcb->key_frame[n].t_frame < mf->external_trigger_pre_capture)
{
if (vcb->key_frame[n].t_frame == 0)
{
@ -475,14 +475,14 @@ video_record_start(VideoCircularBuffer *vcb, int start_state)
if (n == vcb->cur_frame_index)
break;
}
if (t_cur - vcb->key_frame[n].t_frame > mf->external_trigger_pre_capture)
if (vcb->t_cur - vcb->key_frame[n].t_frame > mf->external_trigger_pre_capture)
n = (n + 1) % KEYFRAME_SIZE;
}
else
n = vcb->pre_frame_index;
vcb->record_start_time = vcb->key_frame[n].t_frame;
vcb->record_elapsed_time = t_cur - vcb->record_start_time;
vcb->record_elapsed_time = vcb->t_cur - vcb->record_start_time;
vcb->record_start_frame_index = n;
pikrellcam.video_start_pts = vcb->key_frame[n].frame_pts;
@ -502,7 +502,7 @@ video_record_start(VideoCircularBuffer *vcb, int start_state)
n = vcb->cur_frame_index;
if (vcb->manual_pre_capture > vcb->seconds - 1)
vcb->manual_pre_capture = vcb->seconds - 1;
while (t_cur - vcb->key_frame[n].t_frame < vcb->manual_pre_capture)
while (vcb->t_cur - vcb->key_frame[n].t_frame < vcb->manual_pre_capture)
{
if (vcb->key_frame[n].t_frame == 0)
{
@ -514,14 +514,17 @@ video_record_start(VideoCircularBuffer *vcb, int start_state)
if (n == vcb->cur_frame_index)
break;
}
if (t_cur - vcb->key_frame[n].t_frame > vcb->manual_pre_capture)
if (vcb->t_cur - vcb->key_frame[n].t_frame > vcb->manual_pre_capture)
n = (n + 1) % KEYFRAME_SIZE;
vcb->record_start_time = vcb->key_frame[n].t_frame;
vcb->record_elapsed_time = t_cur - vcb->record_start_time;
vcb->record_elapsed_time = vcb->t_cur - vcb->record_start_time;
vcb->record_start_frame_index = n;
pikrellcam.video_start_pts = vcb->key_frame[n].frame_pts;
// printf("curframe:%d n:%d start:%d cur:%d\n", vcb->cur_frame_index, n,
// (int) vcb->record_start_time, (int)vcb->t_cur);
snprintf(seq_buf, sizeof(seq_buf), "%d",
pikrellcam.video_manual_sequence);
path = media_pathname(pikrellcam.video_dir,
@ -536,6 +539,10 @@ video_record_start(VideoCircularBuffer *vcb, int start_state)
free(path);
path = pikrellcam.video_pathname;
if (pikrellcam.audio_pathname)
free(pikrellcam.audio_pathname);
pikrellcam.audio_pathname = NULL;
if ((s = strstr(path, ".mp4")) != NULL && *(s + 4) == '\0')
{
if (do_stats)
@ -544,6 +551,18 @@ video_record_start(VideoCircularBuffer *vcb, int start_state)
asprintf(&stats_path, "%s.csv", path);
*s = '.';
}
if (pikrellcam.audio_enable)
{
*s = '\0';
asprintf(&pikrellcam.audio_pathname, "%s.mp3", path);
*s = '.';
if (!audio_record_start())
{
free(pikrellcam.audio_pathname);
pikrellcam.audio_pathname = NULL;
}
}
asprintf(&path, "%s.h264", pikrellcam.video_pathname);
dup_string(&pikrellcam.video_h264, path);
free(path);
@ -557,9 +576,11 @@ video_record_start(VideoCircularBuffer *vcb, int start_state)
log_printf("Could not create video file %s. %m\n", path);
else
{
log_printf("Video record: %s ...\n", path);
vcb->state = start_state;
pikrellcam.state_modified = TRUE;
log_printf("%s record start - %s\n",
(start_state == VCB_STATE_MOTION_RECORD_START) ? "Motion" : "Manual",
path);
if ( do_stats
&& (vcb->motion_stats_file = fopen(stats_path, "w")) != NULL
)
@ -579,20 +600,53 @@ video_record_stop(VideoCircularBuffer *vcb)
Event *event = NULL;
char *s, *cmd, *tmp_dir, *detect, *thumb_name, *thumb_cmd = NULL;
int thumb_height;
double ftmp, encode_fps;
if (!vcb->file)
return;
fclose(vcb->file);
vcb->file = NULL;
if (vcb->motion_stats_file)
{
fclose(vcb->motion_stats_file);
vcb->motion_stats_file = NULL;
}
log_printf("Video %s record stopped. Header size: %d h264 file size: %d\n",
(vcb->state & VCB_STATE_MOTION_RECORD) ? "motion" : "manual",
pikrellcam.video_header_size, pikrellcam.video_size);
if (pikrellcam.verbose_motion && !pikrellcam.verbose)
printf("***Motion record stop: %s\n", pikrellcam.video_pathname);
st_h264.st_size = 0;
stat(pikrellcam.video_h264, &st_h264);
/* Get video time and fps stats.
| The pts end-start diff is from frame start of 1st frame to frame start
| of last frame so is the time of frame_count - 1 frames.
*/
pikrellcam.video_last_frame_count = vcb->video_frame_count;
pikrellcam.video_end_pts = vcb->last_pts;
if (pikrellcam.video_last_frame_count > 1)
{
ftmp = (double)(pikrellcam.video_end_pts - pikrellcam.video_start_pts) / 1e6;
ftmp /= pikrellcam.video_last_frame_count - 1;
}
else
ftmp = 0;
ftmp *= pikrellcam.video_last_frame_count;
pikrellcam.video_last_time = ftmp;
if (ftmp > 0)
pikrellcam.video_last_fps =
(double) pikrellcam.video_last_frame_count / ftmp;
else
pikrellcam.video_last_fps = 0;
audio_record_stop();
log_printf("%s record stop - h264 file size:%d vid_time:%.2f vid_fps:%.2f audio_frames:%d audio_rate:%d\n",
(vcb->state & VCB_STATE_MOTION_RECORD) ? "Motion" : "Manual",
pikrellcam.video_size, (float) pikrellcam.video_last_time,
(float) pikrellcam.video_last_fps,
pikrellcam.audio_last_frame_count, pikrellcam.audio_last_rate);
if (vcb->state & VCB_STATE_MOTION_RECORD)
{
if ((mf->first_detect & (MOTION_BURST | MOTION_DIRECTION))
@ -608,11 +662,8 @@ video_record_stop(VideoCircularBuffer *vcb)
mf->direction_detects, mf->burst_detects, mf->max_burst_count);
}
if (pikrellcam.verbose_motion && !pikrellcam.verbose)
printf("***Motion record stop: %s\n", pikrellcam.video_pathname);
st_h264.st_size = 0;
stat(pikrellcam.video_h264, &st_h264);
pikrellcam.state_modified = TRUE;
if (pikrellcam.video_mp4box)
{
@ -633,23 +684,49 @@ video_record_stop(VideoCircularBuffer *vcb)
if ((s = strstr(thumb_name, ".mp4")) != NULL)
strcpy(s, ".th.jpg");
asprintf(&thumb_cmd, "&& avconv -i %s -ss 0 -s 150x%d %s",
asprintf(&thumb_cmd, "&& avconv -i %s -vframes 1 -ss 0 -s 150x%d %s",
pikrellcam.video_pathname, thumb_height,
thumb_name);
free(thumb_name);
}
if ( pikrellcam.camera_adjust.video_mp4box_fps == 0
|| pikrellcam.camera_adjust.video_mp4box_fps
== pikrellcam.camera_adjust.video_fps
)
encode_fps = pikrellcam.video_last_fps;
else
encode_fps = (double) pikrellcam.camera_adjust.video_mp4box_fps;
if (st_h264.st_size > 0) // can be 0 if no space left
asprintf(&cmd, "(MP4Box %s -tmp %s -fps %d -add %s %s %s && rm %s %s)",
pikrellcam.verbose ? "" : "-quiet",
tmp_dir,
(pikrellcam.camera_adjust.video_mp4box_fps > 0) ?
pikrellcam.camera_adjust.video_mp4box_fps :
pikrellcam.camera_adjust.video_fps,
pikrellcam.video_h264, pikrellcam.video_pathname,
pikrellcam.verbose ? "" : "2> /dev/null",
pikrellcam.video_h264,
thumb_cmd ? thumb_cmd : "");
{
if (pikrellcam.audio_pathname)
{
asprintf(&cmd, "(MP4Box %s -tmp %s -fps %.3f -add %s -add %s %s %s && rm %s %s %s)",
pikrellcam.verbose ? "" : "-quiet",
tmp_dir,
(float) encode_fps,
pikrellcam.video_h264,
pikrellcam.audio_pathname,
pikrellcam.video_pathname,
pikrellcam.verbose ? "" : "2> /dev/null",
pikrellcam.video_h264,
pikrellcam.audio_pathname,
thumb_cmd ? thumb_cmd : "");
}
else
{
asprintf(&cmd, "(MP4Box %s -tmp %s -fps %.3f -add %s %s %s && rm %s %s)",
pikrellcam.verbose ? "" : "-quiet",
tmp_dir,
(float) encode_fps,
pikrellcam.video_h264,
pikrellcam.video_pathname,
pikrellcam.verbose ? "" : "2> /dev/null",
pikrellcam.video_h264,
thumb_cmd ? thumb_cmd : "");
}
}
else
asprintf(&cmd, "rm %s", pikrellcam.video_h264);
@ -666,10 +743,7 @@ video_record_stop(VideoCircularBuffer *vcb)
free(cmd);
}
dup_string(&pikrellcam.video_last, pikrellcam.video_pathname);
pikrellcam.video_last_frame_count = vcb->video_frame_count;
pikrellcam.video_end_pts = vcb->last_pts;
pikrellcam.state_modified = TRUE;
pikrellcam.video_notify = TRUE;
event_count_down_add("video saved notify",
@ -710,41 +784,56 @@ video_record_stop(VideoCircularBuffer *vcb)
vcb->pause = FALSE;
}
static boolean
get_arg_pass1(char *arg)
static int
get_arg_pass1(char *opt, char *arg)
{
if (!strcmp(arg, "-quit"))
int args_used = 1;
boolean exec_arg, debug_arg;
if (!strcmp(opt, "-quit"))
quit_flag = TRUE;
if (!strcmp(arg, "-V") || !strcmp(arg, "--version"))
if (!strcmp(opt, "-V") || !strcmp(opt, "--version"))
{
printf("%s\n", PIKRELLCAM_VERSION);
exit(0);
}
else if (!strcmp(arg, "-h") || !strcmp(arg, "--help"))
else if (!strcmp(opt, "-h") || !strcmp(opt, "--help"))
{
/* XXX */
exit(0);
}
else if (!strcmp(arg, "-v"))
debug_arg = TRUE;
if (!strcmp(opt, "-v"))
pikrellcam.verbose = TRUE;
else if (!strcmp(arg, "-vm"))
else if (!strcmp(opt, "-vm"))
pikrellcam.verbose_motion = TRUE;
else if (!strcmp(arg, "-vmulti"))
else if (!strcmp(opt, "-vmulti"))
pikrellcam.verbose_multicast = TRUE;
else if (!strcmp(arg, "-debug"))
else if (!strcmp(opt, "-debug"))
pikrellcam.debug = TRUE;
else if (!strcmp(arg, "-debug-fps"))
else if (!strcmp(opt, "-debug-fps"))
pikrellcam.debug_fps = TRUE;
else if (!strncmp(arg, "-user", 5))
user_uid = atoi(arg + 5);
else if (!strncmp(arg, "-group", 6))
user_gid = atoi(arg + 6);
else if (!strncmp(arg, "-home", 5))
homedir = strdup(arg + 5);
else if (!strcmp(opt, "-ad"))
{
pikrellcam.audio_debug = atoi(arg);
args_used = 2;
}
else
return FALSE;
return TRUE;
debug_arg = FALSE;
exec_arg = TRUE;
if (!strncmp(opt, "-user", 5))
user_uid = atoi(opt + 5);
else if (!strncmp(opt, "-group", 6))
user_gid = atoi(opt + 6);
else if (!strncmp(opt, "-home", 5))
homedir = strdup(opt + 5);
else
exec_arg = FALSE;
return (debug_arg || exec_arg) ? args_used : 0;
}
static void
@ -796,6 +885,7 @@ typedef enum
annotate_string,
delete_log,
fix_thumbs,
audio_cmd,
servo_cmd,
preset_cmd,
multicast,
@ -849,6 +939,7 @@ static Command commands[] =
{ "delete_log", delete_log, 0, TRUE },
{ "fix_thumbs", fix_thumbs, 1, TRUE },
{ "annotate_string", annotate_string, 1, FALSE },
{ "audio", audio_cmd, 1, FALSE },
{ "preset", preset_cmd, 1, FALSE },
{ "servo", servo_cmd, 1, FALSE },
{ "multicast", multicast, 1, TRUE },
@ -1214,6 +1305,10 @@ command_process(char *command_line)
log_printf("Wrong number of args for command: %s\n", command);
break;
case audio_cmd:
audio_command(args);
break;
case preset_cmd:
preset_command(args);
break;
@ -1410,7 +1505,11 @@ make_dir(char *dir)
log_printf_no_timestamp(" make_dir() execing sudo mkdir -p %s\n", dir);
exec_wait("sudo mkdir -p $F", dir);
if ((dir_exists = isdir(dir)) == FALSE)
log_printf_no_timestamp("make_dir(%s) failed.\n", dir);
{
log_printf_no_timestamp("make_dir(%s) failed. %m\n", dir);
if (errno == EOVERFLOW)
log_printf_no_timestamp(" Mount needs nounix,noserverino options? See Help.\n");
}
else
{
if (pikrellcam.verbose)
@ -1442,6 +1541,35 @@ log_start(boolean start_sep, boolean time, boolean end_sep)
log_printf_no_timestamp("========================================================\n");
}
static int
pi_model(void)
{
FILE *f;
static int model;
char buf[200], arm[32];
if (model == 0)
{
if ((f = fopen("/proc/cpuinfo", "r")) != NULL)
{
while (fgets(buf, sizeof(buf), f) != NULL)
{
if (sscanf(buf, "model name %*s %31s", arm) > 0)
{
if (!strncmp(arm, "ARMv6", 5))
model = 1;
else
model = 2;
break;
}
}
fclose(f);
}
}
return model;
}
int
main(int argc, char *argv[])
{
@ -1457,9 +1585,11 @@ main(int argc, char *argv[])
time(&pikrellcam.t_now);
for (i = 1; i < argc; i++)
get_arg_pass1(argv[i]);
for (i = 1; i < argc; )
{
n = get_arg_pass1(argv[i], argv[i+1]);
i += (n > 0) ? n : 1;
}
config_set_defaults(homedir);
if (!config_load(pikrellcam.config_file))
@ -1480,6 +1610,7 @@ main(int argc, char *argv[])
log_printf("setting LC_TIME to %s\n", pikrellcam.lc_time);
setlocale(LC_TIME, pikrellcam.lc_time);
}
pikrellcam.pi_model = pi_model();
/* If need to mmap() gpios for servos, restart a sudo pikrellcam which can
| mmap() /dev/mem and then drop priviledes back to orig user/group
@ -1491,6 +1622,8 @@ main(int argc, char *argv[])
if (user_gid > 0)
setgid(user_gid);
setuid(user_uid);
snprintf(buf, sizeof(buf), "HOME=%s", homedir ? homedir : "/home/pi");
putenv(buf);
log_printf_no_timestamp("== Dropped root priviledges-continuing as normal user ==\n");
log_start(FALSE, FALSE, TRUE);
}
@ -1507,7 +1640,7 @@ main(int argc, char *argv[])
i += sprintf(buf + i, "%s ", *argv++);
set_exec_with_session(FALSE);
exec_wait(buf, NULL); /* restart as root so can mmap() gpios*/
exec_wait(buf, NULL);
exit(0);
}
else if (pikrellcam.servo_use_servoblaster)
@ -1540,8 +1673,11 @@ main(int argc, char *argv[])
for (i = 1; i < argc; i++)
{
if (get_arg_pass1(argv[i]))
if ((n = get_arg_pass1(argv[i], argv[i + 1])) > 0)
{
i += n - 1;
continue;
}
opt = argv[i];
/* Accept: --opt arg -opt arg opt=arg --opt=arg -opt=arg
@ -1593,13 +1729,15 @@ main(int argc, char *argv[])
check_modes(buf, 0775);
asprintf(&pikrellcam.command_fifo, "%s/www/FIFO", pikrellcam.install_dir);
asprintf(&pikrellcam.audio_fifo, "%s/www/audio_FIFO", pikrellcam.install_dir);
asprintf(&pikrellcam.scripts_dir, "%s/scripts", pikrellcam.install_dir);
asprintf(&pikrellcam.scripts_dist_dir, "%s/scripts-dist", pikrellcam.install_dir);
asprintf(&pikrellcam.mjpeg_filename, "%s/mjpeg.jpg", pikrellcam.tmpfs_dir);
asprintf(&pikrellcam.state_filename, "%s/state", pikrellcam.tmpfs_dir);
log_printf_no_timestamp("using FIFO: %s\n", pikrellcam.command_fifo);
log_printf_no_timestamp("using mjpeg: %s\n", pikrellcam.mjpeg_filename);
log_printf_no_timestamp("command FIFO: %s\n", pikrellcam.command_fifo);
log_printf_no_timestamp("audio FIFO : %s\n", pikrellcam.audio_fifo);
log_printf_no_timestamp("mjpeg stream: %s\n", pikrellcam.mjpeg_filename);
/* Subdirs must match www/config.php and the init script is supposed
@ -1645,6 +1783,9 @@ main(int argc, char *argv[])
exit(1);
}
if (!make_fifo(pikrellcam.audio_fifo))
log_printf_no_timestamp("Failed to create audio FIFO.\n");
if ((fifo = open(pikrellcam.command_fifo, O_RDONLY | O_NONBLOCK)) < 0)
{
log_printf("Failed to open FIFO: %s. %m\n", pikrellcam.command_fifo);
@ -1655,6 +1796,12 @@ main(int argc, char *argv[])
read(fifo, buf, sizeof(buf));
camera_start();
if (pikrellcam.audio_enable)
{
if (!audio_mic_open(FALSE))
event_add("audio retry open", pikrellcam.t_now, 5,
audio_retry_open, NULL);
}
config_timelapse_load_status();
preset_state_load();
pikrellcam.state_modified = TRUE;
@ -1662,6 +1809,7 @@ main(int argc, char *argv[])
signal(SIGINT, signal_quit);
signal(SIGTERM, signal_quit);
signal(SIGCHLD, event_child_signal);
signal(SIGPIPE, SIG_IGN);
setup_h264_tcp_server();
setup_mjpeg_tcp_server();

View File

@ -1,6 +1,6 @@
/* PiKrellCam
|
| Copyright (C) 2015-2016 Bill Wilson billw@gkrellm.net
| Copyright (C) 2015-2017 Bill Wilson billw@gkrellm.net
|
| PiKrellCam is free software: you can redistribute it and/or modify it
| under the terms of the GNU General Public License as published by
@ -34,6 +34,7 @@
#include <time.h>
#include <ctype.h>
#include <unistd.h>
#include <stdint.h>
#include <pwd.h>
#include <grp.h>
#include <sys/stat.h>
@ -48,9 +49,12 @@
#include "interface/mmal/util/mmal_default_components.h"
#include "interface/mmal/util/mmal_connection.h"
#include <alsa/asoundlib.h>
#include <lame/lame.h>
#include "utils.h"
#define PIKRELLCAM_VERSION "3.1.4"
#define PIKRELLCAM_VERSION "4.0.0"
//TCP Stream Server
@ -344,6 +348,8 @@ typedef struct
typedef struct
{
int position;
int audio_position;
time_t t_frame;
int frame_count;
uint64_t frame_pts;
@ -365,6 +371,7 @@ typedef struct
{
pthread_mutex_t mutex;
time_t t_cur;
FILE *file,
*motion_stats_file;
boolean motion_stats_do_header;
@ -377,7 +384,7 @@ typedef struct
int8_t *data; /* h.264 video data array */
int size; /* size in bytes of data array */
int seconds; /* max seconds in the buffer */
int seconds; /* max seconds in the buffer */
int head,
tail;
@ -402,6 +409,54 @@ typedef struct
VideoCircularBuffer;
/* Only do 16 bit sound samples
*/
#define SND_FRAMES_TO_BYTES(acb, n_frames) \
((int)(n_frames) * sizeof(int16_t) * acb->channels)
typedef struct
{
pthread_mutex_t mutex;
boolean force_thread_exit;
snd_pcm_t *pcm;
lame_t lame_record,
lame_stream;
FILE *mp3_file;
uint8_t *mp3_record_buffer,
*mp3_stream_buffer;
int mp3_record_buffer_size,
mp3_stream_buffer_size;
int mp3_stream_fd;
int16_t *data; /* circular buffer data */
int data_size; /* and its size in bytes */
int head, /* Frame ndices into circ buf data */
record_head,
tail,
n_frames, /* Size in frames of circ buf data */
max_record_frames;
uint32_t rate, /* ALSA parameters */
channels;
int format,
periods;
unsigned int period_usec;
snd_pcm_uframes_t period_frames,
buffer_frames;
int16_t *buffer; /* holds read of one period of frames */
int buffer_size; /* which is copied to circular buffer */
int frame_count;
int vu_meter;
}
AudioCircularBuffer;
/* -------------- The Global PiKrellCam Environment -----------
*/
typedef struct
@ -467,6 +522,7 @@ typedef struct
t_start;
struct tm tm_local;
int second_tick;
int pi_model;
char *install_dir,
*version,
@ -545,8 +601,13 @@ typedef struct
int video_manual_sequence,
video_motion_sequence,
video_header_size,
video_size,
video_last_frame_count;
video_size;
int video_last_frame_count,
audio_last_frame_count,
audio_last_rate;
double video_last_time,
video_last_fps;
uint64_t video_start_pts,
video_end_pts;
@ -591,6 +652,18 @@ typedef struct
servo_use_servoblaster,
have_servos;
boolean audio_enable,
audio_debug;
char *audio_device,
*audio_pathname,
*audio_fifo;
int audio_rate_Pi2,
audio_rate_Pi1,
audio_channels,
audio_gain_dB,
audio_mp3_quality_Pi2,
audio_mp3_quality_Pi1;
SList *preset_position_list;
int preset_position_index,
n_preset_positions;
@ -722,6 +795,7 @@ extern CameraObject stream_splitter;
extern CameraObject stream_resizer;
extern VideoCircularBuffer video_circular_buffer;
extern AudioCircularBuffer audio_circular_buffer;
extern MotionFrame motion_frame;
extern TimeLapse time_lapse;
@ -755,7 +829,7 @@ void video_h264_encoder_callback(MMAL_PORT_T *port,
MMAL_BUFFER_HEADER_T *mmalbuf);
boolean camera_create(void);
void camera_object_destroy(CameraObject *obj);
void circular_buffer_init(void);
void video_circular_buffer_init(void);
void mmalcam_config_parameters_set_camera(void);
boolean mmalcam_config_parameter_set(char *name, char *value, boolean set_camera);
@ -863,6 +937,17 @@ void servo_move(int pan, int tilt, int delay);
void servo_command(char *args);
void servo_init(void);
boolean audio_mic_open(boolean inform);
void audio_retry_open(void);
void audio_buffer_set_record_head(AudioCircularBuffer *acb, int head);
void audio_buffer_set_tail(AudioCircularBuffer *acb, int position);
boolean audio_record_start(void);
void audio_record_stop(void);
void audio_buffer_set_record_head_tail(AudioCircularBuffer *acb, int head, int tail);
int audio_frames_offset_from_video(AudioCircularBuffer *acb);
void audio_command(char *args);
void audio_circular_buffer_init(void);
void set_exec_with_session(boolean set);
void sun_times_init(void);
void at_commands_config_save(char *config_file);

View File

@ -269,34 +269,6 @@ gpio_write(int pin, int level)
*(gpio_mmap + reg) = 1 << (pin & 0x1f);
}
int
pi_model(void)
{
FILE *f;
static int model;
char buf[200], arm[32];
if (model == 0)
{
if ((f = fopen("/proc/cpuinfo", "r")) != NULL)
{
while (fgets(buf, sizeof(buf), f) != NULL)
{
if (sscanf(buf, "model name %*s %31s", arm) > 0)
{
if (!strcmp(arm, "ARMv7"))
model = 2;
else
model = 1;
break;
}
}
fclose(f);
}
}
return model;
}
static void
_servo_move(int pan, int tilt, int delay)
{
@ -454,7 +426,8 @@ servo_init(void)
pan_channel = tilt_channel = -1;
return;
}
peripheral_base = (pi_model() == 2) ? PI_2_PERIPHERAL_BASE : PI_1_PERIPHERAL_BASE;
peripheral_base = (pikrellcam.pi_model == 2)
? PI_2_PERIPHERAL_BASE : PI_1_PERIPHERAL_BASE;
if ((fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0)
{

34
www/audio_stream.php Normal file
View File

@ -0,0 +1,34 @@
<?php
set_time_limit(0);
$filename = 'audio_FIFO';
$buffer = '';
$readsize = 4096;
header("Content-Type: audio/mpeg");
header('content-type: application/octet-stream');
header ("Content-Transfer-Encoding: binary");
header ("Pragma: no-cache");
header("Cache-Control: no-cache, must-revalidate");
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
$fifo = fopen($filename, 'r');
if ($fifo === false)
{
return false;
}
while (!feof($fifo))
{
$buffer = fread($fifo, $readsize);
echo $buffer;
ob_flush();
flush();
}
$status = fclose($fifo);
return true;
?>

View File

@ -1,7 +1,7 @@
<?php
// Do not edit this file. Edit config-user.php instead.
//
$config_event_count = 18;
$config_event_count = 20;
$n_columns = 4;
$name_style = "short";
@ -26,6 +26,7 @@ $media_thumbs_scrolled = "yes";
$videos_mode = "thumbs";
$video_url = "";
$show_audio_controls = "yes";
$include_control = "no";
function config_user_save()
@ -35,7 +36,7 @@ function config_user_save()
global $n_log_scroll_pixels, $log_text_color, $n_thumb_scroll_pixels, $background_image;
global $config_event_count, $include_control;
global $archive_initial_view, $archive_thumbs_scrolled, $media_thumbs_scrolled, $videos_mode;
global $video_url;
global $video_url, $show_audio_controls;
$file = fopen("config-user.php", "w");
if (!$file)
@ -88,6 +89,7 @@ function config_user_save()
"// The remaining defines here are changed by web page buttons and should not\n"
."// need to be edited here.\n"
."//\n");
fwrite($file, "define(\"SHOW_AUDIO_CONTROLS\", \"$show_audio_controls\");\n\n");
fwrite($file, "define(\"NAME_STYLE\", \"$name_style\");\n");
fwrite($file, "define(\"N_COLUMNS\", \"$n_columns\");\n");
fwrite($file, "define(\"VIDEOS_MODE\", \"$videos_mode\");\n");
@ -151,6 +153,9 @@ if (defined('BACKGROUND_IMAGE'))
if (defined('VIDEO_URL'))
$video_url = VIDEO_URL;
if (defined('SHOW_AUDIO_CONTROLS'))
$show_audio_controls = SHOW_AUDIO_CONTROLS;
if (defined('INCLUDE_CONTROL'))
$include_control = INCLUDE_CONTROL;

View File

@ -65,7 +65,14 @@ Under construction...
</div>
<span style='font-size: 1.5em; font-weight: 650;'>Release Notes</span><hr>
<div class='indent0'>
<div class='indent0'> Version 4.0
<div class='indent1'>
<a href="help.php#AUDIO">Audio recording</a> - For Jessie Lite & Minibian
users, libmp3lame0 and libasound2 need to be installed or else after
upgrading to PiKrellCam 4.0, a restart will fail.
Rerun the install script or install by apt-get.
</div>
Version 3.1
<div class='indent1'>
<a href="help.php#MULTICAST_INTERFACE">multicast interface</a><br>
@ -73,41 +80,6 @@ Version 3.1
</div>
</div>
Version 3.0
<div class='indent1'>
The upgrade from PiKrellcam V2.x to PiKrellCam V3.0 adds presets and servo control.<br>
This is documented below on this page, but there are changes in how
motion regions usage is handled that is important to be aware of up front:
<ul>
<li> The use of saving and loading of motion regions by name is no longer the primary
way to change the motion regions in effect. Saving and loading regions by name now
has a new role of maintaining a set of motion regions as temporaries for backup or
using as an initial condition to be loaded when creating a new preset.
</li>
<li> If motion regions are edited or a new set loaded by name, the changes are
automatically stored into the current preset (unless you have servos and are off
a preset - see below). So if you edit motion regions the current preset is
changed and there is no need to save by name unless you want the backup.
</li>
<li> When pikrellcam is restarted, it loads the preset you were on when pikrellcam
stopped. If for example, you have a preset 1 set up for default use and a preset 2
for windy conditions, then if you want to be sure that at program start preset 1 is
selected, you should have in at-commands.conf:
<pre>
daily start "@preset goto 1 1"
</pre>
If you have servos, you might want to have a position number other than "1".<br>
This would replace the use of an at command to load regions and if you have such
a startup motion load_regions command, you probably want to take that out. If you
don't take it out and don't have a startup preset goto, the regions will be loaded
into whatever preset you restart with which can be not what you want.
If you do use a startup
preset goto command, also having a startup motion load_regions is likely redundant
because the preset remembers its motion regions.
</li>
</ul>
</div>
</div>
<span style='font-size: 1.5em; font-weight: 650;'>Install</span><hr>
@ -520,6 +492,8 @@ again or the servo reaches a pan/tilt limit.
</div>
<span style='font-size: 1.5em; font-weight: 650;'>Motion Regions Panel</span><hr>
<img src="images/motion-regions.jpg" alt="motion-regions.jpg">
<div class='indent0'>
@ -840,6 +814,218 @@ Preset group and there will be no Servo button in the Config group.
</div>
<a name="AUDIO">
<span style='font-size: 1.5em; font-weight: 650;'>Audio</span><hr>
<div class='indent0'>
Pikrellcam can do two independent audio MP3 encodes. One encode
is for recording audio with videos and is sourced from PCM sound data stored
in a circular buffer so there can be in sync pre-captured sound to track
video pre-capture. The other encode is for streaming MP3 sound to a browser
(but see Issues) and is sourced from PCM sound data as it is read
from the sound capture device.
The streamed audio is not in sync with the mjpeg image stream
displayed by the browser because audio buffering will cause a
couple of seconds delay.
<p>
Connect an ALSA input capture device such as
USB sound card + microphone, USB mini microphone, or other which can
be recognized by running
<span style='font-weight:700'>arecord -l</span>.
If arecord can record from the device and aplay play the wav file,
then pikrellcam should work using the same sound device. You may need to
run alsamixer to unmute the microphone input and set the capture level.
If you need more information than the basic setup listed here, the web
has many Pi microphone tutorials to look at.
<br>
So a microphone setup for PiKrellCam is:
<ul>
<li>
The user <span style='font-weight:700'>pi</span>
must be in the audio group.<br>
Connect the USB sound card + microphone and the kernel should
load the USB sound modules.
Verify the sound card number using arecord.
I get card 1 and use that for the remaining examples:
<pre>
pi@rpi2: ~$ arecord -l | grep USB
card 1: Device [USB PnP Sound Device], device 0: USB Audio [USB Audio]
</pre>
</li>
<li> Run alsamixer on card 1 and make sure the microphone input is
not muted. Probably set the input sensitivy high.
In alsamixer, press F4 to show controls
for the microphone capture input for the card.
<pre>
pi@rpi2: ~$ alsamixer -c 1
</pre>
</li>
<li>
For sound card 1, the ALSA hardware device is plughw:1.
Test that device by recording and playing an audio wav file:
<pre>
# Do a basic 5 second record:
pi@rpi2: ~$ arecord -D plughw:1 -d 5 test.wav
# And also check using parameters the same as pikrellcam recording defaults,
# defaults are: device: plughw:1 channels: 1 rate: 24000 or 48000 16 bit audio.
arecord -D plughw:1 -d 5 -c 1 -f s16_LE -r 24000 test.wav
# Play the sound:
pi@rpi2: ~$ aplay test.wav
</pre>
</li>
<li> If the USB sound card is not plughw:1, edit
audio_device in pikrellcam.conf to be the correct device
and restart pikrellcam.
</li>
<li> Enable/disable audio recording is done by clicking the microphone
audio control toggle button on the web page. An audio VU meter is
drawn on the OSD when the microphone is successfully opened.
</li>
</ul>
<p>
<span style='font-size: 1.2em; font-weight: 680;'>Audio Control Buttons</span><br>
<div class='indent1'>
<img src="images/audio-controls.jpg" alt="audio-controls.jpg">
Web page audio control buttons are left to right:
<ul>
<li><span style='font-weight:700'>Audio Stream Stop</span> -
The OSD moving stream indicator will disappear.
</li>
<li><span style='font-weight:700'>Audio Stream Play</span> - If the
microphone is open, PCM audio is encoded into MP3 audio
which can be played by a browser (see Issues). When streaming is on,
the OSD shows a moving streaming indicator under the VU meter and
gain value.
</li>
<li><span style='font-weight:700'>Microphone Toggle</span> - Opens and
closes the microphone. When the microphone is open, the OSD shows a
vertical audio VU meter with the current gain value printed under
it. With the microphone opened, audio is recorded with videos and
can be streamed.
</li>
<li><span style='font-weight:700'>Audio Gain Up</span> - Increment the
audio gain up to 30dB.
This is not the gain set by alsamixer but is an additional
amplication of the PCM sound data read from ALSA to help boost audio
at the expense of amplified noise and risk of distortion from clipping.
</li>
<li><span style='font-weight:700'>Audio Gain Down</span> - Decrement the
audio gain in dB to a minimum of 0dB (amplification factor is 1).
</li>
</ul>
</div>
<span style='font-size: 1.2em; font-weight: 680;'>Audio Parameters in pikrellcam.conf</span><br>
<div class='indent1'>
Edit pikrellcam.conf to change these settings:
<ul>
<li><span style='font-weight:700'>audio_device</span> - default: plughw:1<br>
Sets the ALSA hardware audio input (microphone) capture device.
</li>
<li><span style='font-weight:700'>audio_rate_Pi2</span> - default: 48000<br>
<span style='font-weight:700'>audio_rate_Pi1</span> - default: 24000<br>
Audio sample rate used for Pi model 1 and Pi model 2.
Lame docs suggest using only MP3 supported sample rates:<br>
&nbsp;&nbsp;&nbsp; 8000 11025 12000 16000 22050 24000 32000 44100 48000<br>
</li>
<li><span style='font-weight:700'>audio_channels</span> - default: 1<br>
Set to 1
for mono or 2 (not tested yet) for stereo. Probably
most USB sound card
microphone inputs will support only one channel and setting 2 will
be reverted to 1 when pikrellcam opens the microphone.
</li>
<li><span style='font-weight:700'>audio_mp3_quality_Pi2</span> - default: 2<br>
<span style='font-weight:700'>audio_mp3_quality_Pi1</span> - default: 7<br>
Value for quality of the lame lib encode of PCM to MP3 audio for
Pi model 1 and Pi model 2.
Values range from 0 (best quality but very slow encode) to 9
(worst qualilty but fast encode). Lame docs say 2 is near best and
not too slow and 7 is OK quality and a really fast encode.
</li>
</ul>
Pi models 1 (ARMv6 single core) and 2 (ARMv7 quad core) have separate settings
for sample rate and encode quality because two simultaneous audio MP3
encodings can push a model 1 to very high CPU usage. A Pi2/3 does not have
a CPU usage issue. This image shows CPU usage for a Pi1
to give an idea of what to expect. Streaming audio while recording
(R2 interval: two MP3 encodes) uses high CPU and causes
an extended video conversion time (C2 interval: one MP3 encode).
It is the particular Pi1 use case of expected video length/frequency and
any overclocking that determines what audio sample rate and encode
quality should be set and whether it is wise to stream audio
while videos are recording.
<p>
<img src="images/cpu-usage.jpg" alt="cpu-usage.jpg">
</div>
<p>
<span style='font-size: 1.2em; font-weight: 680;'>Limitations & Issues</span><br>
<div class='indent1'>
<ul>
<li> Audio MP3 streaming works for me to Firefox but not to Chromium
for some reason. Don't know if YMMV on this.
</li>
<li> FYI, sometimes clicking the microphone toggle button can fail to open
the microphone because the device is busy, but clicking it
some more eventually succeeds.
Running arecord can similarly fail with device busy so it may
be some issue with USB sound cards.
<li> If a video has out of sync audio, check the log to see if
the actual video fps and audio rate was what is configured.
If these are off, then data has been lost during the record. This
is likely more a possible issue on a Pi1.
</li>
<li> Audio cannot be streamed to more than one web page at a time.
</li>
<li> ALSA audio capture devices cannot be opened by more than one
application. If a microphone is needed for another purpose, it
cannot also be open in pikrellcam.
</li>
</ul>
</div>
<p>
<span style='font-size: 1.2em; font-weight: 680;'>Microphones</span><br>
<div class='indent1'>
A high
<a href="https://geoffthegreygeek.com/microphone-sensitivity/">
microphone sensitivity</a> is likely needed for a PiKrellCam application
which wants to pick up related audio when recording events at some
distance from the camera.
A cheap way to experiment is to order some
<a href="http://www.hobby-hour.com/electronics/computer_microphone.php">
electret microphones</a>
from an electronics supplier and solder them to a 3.5mm plug. I have been
using electrets with sensitivities from
<a href="http://www.mouser.com/ProductDetail/DB-Unlimited/MO093803-1/?qs=sGAEpiMZZMvxTCYhU%252bW9md6RLkZl0Nse48Qi7C4xp2w%3d">
-38dB</a> to
<a href="http://www.mouser.com/ProductDetail/CUI/CMC-6027-24T/?qs=sGAEpiMZZMuCv89HBVkAk5iC6ZN50VtrfpvYg8FFM2E%3d">
-24dB</a>
ordered from Mouser, but similar ones should be available from other
suppliers near you. USB sound cards I have tried so far have the
microphone jack tip internally shorted to the ring. To check, connect the
plug to the sound card and measure for zero resistance between the tip and
ring lugs. So I simply solder the microphone (-) terminal to plug ground
and the microphone (+) terminal to either the plug tip or ring.
<p>
<img src="images/electret.jpg" alt="electret.jpg">
<p>
Noise is a possible issue when using sensitive omnidirectional microphones.
A couple of likely causes are power line hum from the surrounding
microphone environment or electrical noise getting into the USB sound
card through the power supply.
So microphone placement and a clean power supply can be important.
</div>
</div>
<span style='font-size: 1.5em; font-weight: 650;'>Configuration Files</span><hr>
<div class='indent0'>
<span style='font-size: 1.2em; font-weight: 650;'>~/.pikrellcam/pikrellcam.conf</span>
@ -965,42 +1151,57 @@ archive_dir archive
</pre>
With this setup, all media files are stored on the Pi SD card. Media files may be configured to
be stored on an external USB disk by editing the <nobr>~/pikrellcam/scripts/startup</nobr> file to uncomment
be stored on an external disk by editing the <nobr>~/pikrellcam/scripts/startup</nobr> file to uncomment
the line:
<pre>
MOUNT_DISK=sda1
</pre>
<p>
This assumes there is a single USB disk plugged into the Pi and it appears as
<nobr>/dev/sda</nobr>. If this USB disk has a linux filesystem on
<nobr>/dev/sda</nobr>.
If this USB disk has a linux filesystem on
<nobr>/dev/sda1</nobr>, pikrellcam can create directories with the
needed permissions. However, if the filesystem is not a linux filesystem
(eg. VFAT or FAT32) then pikrellcam cannot set up the needed permissions for the
web interface to work and media files will not be shown
(eg. VFAT or FAT32) then pikrellcam cannot set up the needed permissions for
the web interface to work and media files will not be shown
unless the proper permissions are specified when
the partition is mounted. For this case, the mount command or fstab entry
should specify umask or dmask/fmask permissions of 0002. For example,
use a mount command like:
the partition is mounted.
For this case, the mount command or fstab entry
should specify umask or dmask/fmask permissions of 0002.
<p>
Also, the mount point can be somewhere else in the filesystem. As an
example, you want a VFAT disk to be mounted on /media/mountdir.
For this, use absolute pathnames in pikrellcam.conf:
<pre>
media_dir /media/mountdir
archive_dir /media/mountdir/archive
</pre>
In the startup script, use a mount command like:
<pre>
sudo mount -t vfat /dev/sda1 /media/mountdir -o rw,user,umask=0002
</pre>
or, if using fstab, the entry should be like:
or, if using fstab instead of the startup script, the entry should be like:
<pre>
/dev/sda1 /media/mountdir vfat rw,user,umask=0002 0 0
</pre>
You can use dmask=0002,fmask=0002 in place of umask=0002.<br>
With a disk mounted, you can see:
<pre>
pi@rpi2: ~$ df -h
Filesystem Size Used Avail Use% Mounted on
...
/dev/sda1 3.7G 1.9G 1.6G 54% /home/pi/pikrellcam/media
</pre>
<p>
and the media links in <nobr>~/pikrellcam/www</nobr> will now be pointing into the mounted USB disk.
The media links may be changed to point to some other part of the filesystem which can be
mounted with a USB disk or NAS. Just change the media_dir or archive_dir values in
pikrellcam.conf to reference an absolute path.
If mounting a large CIFS filesystem the nounix,noserverino options may
be needed in fstab so pikrellcam can make directories. Look at the example
fstab entry forum
<a href="https://www.raspberrypi.org/forums/viewtopic.php?p=1123960#p1123960">
raspberry pi forum</a>
<p>
The archive directory can be on a disk different from the media directory. For example
to have the media directory on a USB disk mounted on the ~/pikrellcam/media directory and
the archive directory on a NAS mounted on /mnt/SHARE, in pikrellcam.conf:
<pre>
media_dir media
archive_dir /mnt/SHARE/archive
</pre>
The mount of the USB disk could be handled in the startup script and the
NAS drive mounted in the script or using fstab as described above.
</div><br><br>
<span style='font-size: 1.5em; font-weight: 500;'>Archiving</span><hr>
@ -1098,6 +1299,12 @@ a communication pipe named
<p>
List of <span style='font-weight:700'>FIFO</span> commands:
<pre>
audio mic_open
audio mic_close
audio mic_toggle
audio gain [up|down|N] # range 0 - 30
audio stream_open
audio stream_close
record on
record on pre_capture_time
record on pre_capture_time time_limit

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 111 KiB

BIN
www/images/audio-blank.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 815 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
www/images/audio-play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

BIN
www/images/audio-stop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

BIN
www/images/cpu-usage.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
www/images/electret.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
www/images/mic-down.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

BIN
www/images/mic-up.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

BIN
www/images/mic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

View File

@ -91,7 +91,18 @@ function time_lapse_period()
</head>
<?php
echo "<body background=\"$background_image\" onload=\"mjpeg_start();\">";
if (isset($_GET["hide_audio"]))
{
$show_audio_controls = "no";
config_user_save();
}
if (isset($_GET["show_audio"]))
{
$show_audio_controls = "yes";
config_user_save();
}
echo "<body background=\"$background_image\" onload=\"mjpeg_start();\">";
echo "<div class=\"text-center\">";
echo "<div class='text-shadow-large'>";
echo TITLE_STRING;
@ -102,28 +113,60 @@ echo "<body background=\"$background_image\" onload=\"mjpeg_start();\">";
style=\"border:4px groove silver;\"
onclick=\"image_expand_toggle();\"
></div>";
echo "<div class=\"text-center top-margin\">";
if (defined('SHOW_AUDIO_CONTROLS'))
{
if ($show_audio_controls == "yes")
{
echo "<audio id=\"audio_fifo\" controls src=\"audio_stream.php\"
hidden=\"hidden\" preload=\"none\" type=\"audio/mpeg\" >
MP3 not supported </audio>";
echo "<input type=\"image\" src=\"images/audio-stop.png\"
style=\"vertical-align: bottom; margin-left:0px;\"
onclick=\"audio_stop()\"
width=\"18\" height=\"28\">";
echo "<input type=\"image\" src=\"images/audio-play.png\"
style=\"vertical-align: bottom; margin-left:3px;\"
onclick=\"audio_play()\"
width=\"18\" height=\"28\">";
echo "<input type=\"image\" src=\"images/mic.png\"
style=\"vertical-align: bottom; margin-left:10px;\"
onclick=\"fifo_command('audio mic_toggle')\"
width=\"18\" height=\"28\">";
echo "<input type=\"image\" src=\"images/mic-up.png\"
style=\"vertical-align: bottom; margin-left:3px;\"
onclick=\"fifo_command('audio gain up')\"
width=\"18\" height=\"28\">";
echo "<input type=\"image\" src=\"images/mic-down.png\"
style=\"vertical-align: bottom; margin-left:3px;\"
onclick=\"fifo_command('audio gain down')\"
width=\"18\" height=\"28\">";
}
}
?>
<div class="text-center top-margin">
<input type="image" src="images/stop.png"
style="vertical-align: bottom;"
style="vertical-align: bottom; margin-left:20px;"
onclick="fifo_command('record off')"
width="30" height="30"
width="28" height="28"
>
<input type="image" src="images/pause.png"
style="vertical-align: bottom;"
onclick="fifo_command('pause')"
width="30" height="30"
width="28" height="28"
>
<input type="image" src="images/record.png"
style="vertical-align: bottom;"
onclick="fifo_command('record on')"
width="30" height="30"
width="28" height="28"
>
<input type="image" src="images/shutter.png"
width="30" height="30"
width="28" height="28"
onclick="fifo_command('still')"
style="margin-left:16px; vertical-align: bottom;"
style="margin-left:12px; vertical-align: bottom;"
>
<?php
@ -611,6 +654,14 @@ if ($servos_enable == "servos_on")
onclick="fifo_command('halt')"
class="btn-control alert-control"
>
<?php
echo "<span style='float:right;'>";
if ("$show_audio_controls" == "yes")
echo "<a href='index.php?hide_audio'>Hide Audio</a>";
else
echo "<a href='index.php?show_audio'>Show Audio</a>";
echo "</span>";
?>
</div>
</div>
<?php

View File

@ -71,6 +71,26 @@ function mjpeg_start()
}
function audio_play()
{
var audio_file = document.getElementById("audio_fifo");
audio_file.src = document.getElementById("audio_fifo").src;
audio_file.play();
fifo_command("audio stream_open");
}
function audio_stop()
{
var audio_file = document.getElementById("audio_fifo");
fifo_command("audio stream_close");
audio_fifo.pause();
audio_fifo.currentTime = 0;
}
function create_XMLHttpRequest()
{
if (window.XMLHttpRequest)