Audio/video recording. Help page has audio docs.
Version 4.0: add audio recording to videos and audio streaming to a browser.
|
@ -1,6 +1,7 @@
|
||||||
scripts
|
scripts
|
||||||
www/.htpasswd
|
www/.htpasswd
|
||||||
www/FIFO
|
www/FIFO
|
||||||
|
www/audio_FIFO
|
||||||
www/media
|
www/media
|
||||||
www/config-user*
|
www/config-user*
|
||||||
www/images/bg_*
|
www/images/bg_*
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
# PiKrellCam
|
# PiKrellCam
|
||||||
|
|
||||||
PiKrellCam is a video recording motion detect program with an OSD
|
PiKrellCam is an audio/video recording motion detect program with an OSD web
|
||||||
web interface that detects motion using the Raspberry Pi camera
|
interface that detects motion using the Raspberry Pi camera MMAL motion vectors.
|
||||||
MMAL motion vectors.
|
|
||||||
|
|
||||||
Read about it and install instructions at:
|
Read about it and install instructions at:
|
||||||
[PiKrellCam webpage](http://billw2.github.io/pikrellcam/pikrellcam.html)
|
[PiKrellCam webpage](http://billw2.github.io/pikrellcam/pikrellcam.html)
|
||||||
|
|
|
@ -111,7 +111,9 @@ echo "Starting PiKrellCam install..."
|
||||||
# =============== apt install needed packages ===============
|
# =============== apt install needed packages ===============
|
||||||
#
|
#
|
||||||
PACKAGE_LIST=""
|
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
|
do
|
||||||
if ! dpkg -s $PACKAGE 2>/dev/null | grep Status | grep -q installed
|
if ! dpkg -s $PACKAGE 2>/dev/null | grep Status | grep -q installed
|
||||||
then
|
then
|
||||||
|
@ -124,7 +126,7 @@ then
|
||||||
echo "Installing packages: $PACKAGE_LIST"
|
echo "Installing packages: $PACKAGE_LIST"
|
||||||
echo "Running: apt-get update"
|
echo "Running: apt-get update"
|
||||||
sudo 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
|
else
|
||||||
echo "No packages need to be installed."
|
echo "No packages need to be installed."
|
||||||
fi
|
fi
|
||||||
|
@ -140,7 +142,7 @@ then
|
||||||
if ! dpkg -s realpath 2>/dev/null | grep Status | grep -q installed
|
if ! dpkg -s realpath 2>/dev/null | grep Status | grep -q installed
|
||||||
then
|
then
|
||||||
echo "Installing package: realpath"
|
echo "Installing package: realpath"
|
||||||
sudo apt-get install -y realpath
|
sudo apt-get install -y --no-install-recommends realpath
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
BIN
pikrellcam
|
@ -72,4 +72,12 @@ else
|
||||||
fi
|
fi
|
||||||
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
|
echo "inform timeout $TIMEOUT" > $FIFO
|
||||||
|
|
|
@ -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
|
|
@ -32,12 +32,15 @@ THUMB_JPEG=$5
|
||||||
# OR, to email the smaller motion area thumb jpg, uncomment this line:
|
# OR, to email the smaller motion area thumb jpg, uncomment this line:
|
||||||
# EMAIL_JPEG=$THUMB_JPEG
|
# 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
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -21,10 +21,11 @@ MMAL_LIB ?= -L/opt/vc/lib -lbcm_host -lvcos -lmmal -lmmal_core -lmmal_util \
|
||||||
|
|
||||||
|
|
||||||
FLAGS = -O2 -Wall $(MMAL_INCLUDE) $(INCLUDES)
|
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 \
|
LOCAL_SRC = pikrellcam.c mmalcam.c motion.c event.c display.c config.c \
|
||||||
preset.c sunriset.c multicast.c tcpserver.c tcpserver.c tcpserver_mjpeg.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)))
|
KRELLMLIB_SRC = $(wildcard $(addsuffix /*.c,$(LIBKRELLM_DIRS)))
|
||||||
SOURCES = $(LOCAL_SRC) $(KRELLMLIB_SRC)
|
SOURCES = $(LOCAL_SRC) $(KRELLMLIB_SRC)
|
||||||
|
|
|
@ -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(¶ms)) < 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;
|
||||||
|
}
|
||||||
|
}
|
54
src/config.c
|
@ -1,6 +1,6 @@
|
||||||
/* PiKrellCam
|
/* 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
|
| PiKrellCam is free software: you can redistribute it and/or modify it
|
||||||
| under the terms of the GNU General Public License as published by
|
| 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 },
|
"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# ------------------- Miscellaneous Options -----------------------\n"
|
||||||
"#\n"
|
"#\n"
|
||||||
"# How long in seconds a notify string should stay on the stream jpeg file.\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)
|
if ((f = fopen(config_file, "r")) == NULL)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
pikrellcam.config_sequence_new = 37;
|
pikrellcam.config_sequence_new = 40;
|
||||||
|
|
||||||
while (fgets(linebuf, sizeof(linebuf), f))
|
while (fgets(linebuf, sizeof(linebuf), f))
|
||||||
{
|
{
|
||||||
|
@ -1183,6 +1227,12 @@ config_load(char *config_file)
|
||||||
else if (pikrellcam.annotate_text_brightness > 255)
|
else if (pikrellcam.annotate_text_brightness > 255)
|
||||||
pikrellcam.annotate_text_size = 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 = '_';
|
pikrellcam.annotate_string_space_char = '_';
|
||||||
|
|
||||||
camera_adjust_temp = pikrellcam.camera_adjust;
|
camera_adjust_temp = pikrellcam.camera_adjust;
|
||||||
|
|
|
@ -506,6 +506,49 @@ display_preset_settings(void)
|
||||||
JUSTIFY_RIGHT(0), info1);
|
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
|
static void
|
||||||
inform_draw(void)
|
inform_draw(void)
|
||||||
{
|
{
|
||||||
|
@ -1133,9 +1176,19 @@ apply_adjustment(void)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
pthread_mutex_lock(&vcb->mutex);
|
pthread_mutex_lock(&vcb->mutex);
|
||||||
pikrellcam.motion_times.pre_capture = motion_times_temp.pre_capture;
|
if (vcb->state == VCB_STATE_NONE)
|
||||||
pikrellcam.motion_times.event_gap = motion_times_temp.event_gap;
|
{
|
||||||
circular_buffer_init();
|
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);
|
pthread_mutex_unlock(&vcb->mutex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1773,15 +1826,32 @@ display_inform(char *args)
|
||||||
{
|
{
|
||||||
InformLine *iline;
|
InformLine *iline;
|
||||||
char str[128];
|
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)
|
if (sscanf(args, "timeout %d\n", &n) == 1)
|
||||||
{
|
{
|
||||||
n = (n <= 0 || n > 30) ? pikrellcam.notify_duration : n;
|
n = (n < 0 || n > 30) ? pikrellcam.notify_duration : n;
|
||||||
event_count_down_add("display inform expire",
|
if (n == 0)
|
||||||
n * EVENT_LOOP_FREQUENCY, display_inform_expire, NULL);
|
{
|
||||||
|
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",
|
n = sscanf(args, "\"%127[^\"]\" %d %d %d %d %d",
|
||||||
str, &row, &justify, &font, &xs, &ys);
|
str, &row, &justify, &font, &xs, &ys);
|
||||||
|
@ -1861,6 +1931,7 @@ display_draw(uint8_t *i420)
|
||||||
display_servo_pan();
|
display_servo_pan();
|
||||||
display_servo_tilt();
|
display_servo_tilt();
|
||||||
}
|
}
|
||||||
|
display_audio();
|
||||||
display_preset_setting();
|
display_preset_setting();
|
||||||
|
|
||||||
display_action = ACTION_NONE;
|
display_action = ACTION_NONE;
|
||||||
|
|
27
src/event.c
|
@ -1,6 +1,6 @@
|
||||||
/* PiKrellCam
|
/* 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
|
| PiKrellCam is free software: you can redistribute it and/or modify it
|
||||||
| under the terms of the GNU General Public License as published by
|
| 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",
|
asprintf(&pikrellcam.preview_thumb_filename, "%s/%s",
|
||||||
pikrellcam.thumb_dir, base);
|
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.mjpeg_filename,
|
||||||
pikrellcam.preview_filename);
|
pikrellcam.preview_filename);
|
||||||
if ((f_dst = fopen(pikrellcam.preview_filename, "w")) != NULL)
|
if ((f_dst = fopen(pikrellcam.preview_filename, "w")) != NULL)
|
||||||
|
@ -542,7 +542,6 @@ state_file_write(void)
|
||||||
PresetSettings *settings = NULL;
|
PresetSettings *settings = NULL;
|
||||||
char *state;
|
char *state;
|
||||||
int pan, tilt;
|
int pan, tilt;
|
||||||
double ftmp, fps;
|
|
||||||
|
|
||||||
if (!fname_part)
|
if (!fname_part)
|
||||||
asprintf(&fname_part, "%s.part", pikrellcam.state_filename);
|
asprintf(&fname_part, "%s.part", pikrellcam.state_filename);
|
||||||
|
@ -592,24 +591,10 @@ state_file_write(void)
|
||||||
fprintf(f, "video_last %s\n",
|
fprintf(f, "video_last %s\n",
|
||||||
pikrellcam.video_last ? pikrellcam.video_last : "none");
|
pikrellcam.video_last ? pikrellcam.video_last : "none");
|
||||||
fprintf(f, "video_last_frame_count %d\n", pikrellcam.video_last_frame_count);
|
fprintf(f, "video_last_frame_count %d\n", pikrellcam.video_last_frame_count);
|
||||||
|
fprintf(f, "video_last_time %.2f\n", (float) pikrellcam.video_last_time);
|
||||||
/* The pts end-start diff is from frame start of 1st frame to frame start
|
fprintf(f, "video_last_fps %.2f\n", (float) pikrellcam.video_last_fps);
|
||||||
| of last frame so is the time of frame_count - 1 frames.
|
fprintf(f, "audio_last_frame_count %d\n", pikrellcam.audio_last_frame_count);
|
||||||
*/
|
fprintf(f, "audio_last_rate %d\n", pikrellcam.audio_last_rate);
|
||||||
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, "still_last %s\n",
|
fprintf(f, "still_last %s\n",
|
||||||
pikrellcam.still_last ? pikrellcam.still_last : "none");
|
pikrellcam.still_last ? pikrellcam.still_last : "none");
|
||||||
|
|
105
src/mmalcam.c
|
@ -1,4 +1,4 @@
|
||||||
/* PiKrellCam
|
/* PiKrellCam
|
||||||
|
|
|
|
||||||
| Copyright (C) 2015-2016 Bill Wilson billw@gkrellm.net
|
| 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
|
void
|
||||||
circular_buffer_init()
|
video_circular_buffer_init()
|
||||||
{
|
{
|
||||||
VideoCircularBuffer *vcb = &video_circular_buffer;
|
VideoCircularBuffer *vcb = &video_circular_buffer;
|
||||||
int i, seconds, size;
|
int i, seconds, size;
|
||||||
|
@ -412,31 +412,34 @@ circular_buffer_init()
|
||||||
*/
|
*/
|
||||||
seconds = MAX(pikrellcam.motion_times.event_gap,
|
seconds = MAX(pikrellcam.motion_times.event_gap,
|
||||||
pikrellcam.motion_times.pre_capture) + 5;
|
pikrellcam.motion_times.pre_capture) + 5;
|
||||||
size = pikrellcam.camera_adjust.video_bitrate * seconds / 8;
|
|
||||||
vcb->seconds = seconds;
|
vcb->seconds = seconds;
|
||||||
|
|
||||||
|
size = pikrellcam.camera_adjust.video_bitrate * seconds / 8;
|
||||||
if (size != vcb->size)
|
if (size != vcb->size)
|
||||||
{
|
{
|
||||||
if (vcb->data)
|
if (vcb->data)
|
||||||
free(vcb->data);
|
free(vcb->data);
|
||||||
vcb->data = (int8_t *)malloc(size);
|
vcb->data = (int8_t *) malloc(size);
|
||||||
log_printf("circular buffer allocate: %.2f MBytes (%d seconds at %.1f Mbits/sec)\n",
|
log_printf("video circular buffer - %.2f MB (%d seconds, %.1f Mbits/sec)\n",
|
||||||
(float) size / 1000000.0, seconds,
|
(float) size / 1000000.0, seconds,
|
||||||
(double)pikrellcam.camera_adjust.video_bitrate / 1000000.0);
|
(double)pikrellcam.camera_adjust.video_bitrate / 1000000.0);
|
||||||
}
|
}
|
||||||
|
vcb->size = size;
|
||||||
|
vcb->head = vcb->tail = 0;
|
||||||
|
|
||||||
if (!vcb->data)
|
if (!vcb->data)
|
||||||
{
|
{
|
||||||
log_printf("Aborting because circular buffer malloc() failed.\n");
|
log_printf("Aborting because video circular buffer malloc() failed.\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
vcb->size = size;
|
|
||||||
vcb->head = 0;
|
|
||||||
vcb->cur_frame_index = 0;
|
vcb->cur_frame_index = 0;
|
||||||
vcb->pre_frame_index = 0;
|
vcb->pre_frame_index = 0;
|
||||||
vcb->in_keyframe = FALSE;
|
vcb->in_keyframe = FALSE;
|
||||||
for (i = 0; i < KEYFRAME_SIZE; ++i)
|
for (i = 0; i < KEYFRAME_SIZE; ++i)
|
||||||
{
|
{
|
||||||
vcb->key_frame[i].position = 0;
|
vcb->key_frame[i].position = 0;
|
||||||
|
vcb->key_frame[i].audio_position = 0;
|
||||||
vcb->key_frame[i].t_frame = 0;
|
vcb->key_frame[i].t_frame = 0;
|
||||||
vcb->key_frame[i].frame_count = 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.
|
/* Write circular buffer data from the tail to head and upate the tail.
|
||||||
*/
|
*/
|
||||||
void
|
static void
|
||||||
vcb_video_write(VideoCircularBuffer *vcb)
|
video_buffer_write(VideoCircularBuffer *vcb)
|
||||||
{
|
{
|
||||||
if (!vcb || !vcb->file)
|
if (!vcb || !vcb->file)
|
||||||
return;
|
return;
|
||||||
|
@ -487,12 +490,12 @@ void
|
||||||
video_h264_encoder_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *mmalbuf)
|
video_h264_encoder_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *mmalbuf)
|
||||||
{
|
{
|
||||||
VideoCircularBuffer *vcb = &video_circular_buffer;
|
VideoCircularBuffer *vcb = &video_circular_buffer;
|
||||||
|
AudioCircularBuffer *acb = &audio_circular_buffer;
|
||||||
MotionFrame *mf = &motion_frame;
|
MotionFrame *mf = &motion_frame;
|
||||||
KeyFrame *kf;
|
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;
|
int t_usec, dt_frame;
|
||||||
boolean force_stop;
|
boolean force_stop;
|
||||||
time_t t_cur = pikrellcam.t_now;
|
|
||||||
static int fps_count, pause_frame_count_adjust;
|
static int fps_count, pause_frame_count_adjust;
|
||||||
static time_t t_sec, t_prev;
|
static time_t t_sec, t_prev;
|
||||||
uint64_t t64_now;
|
uint64_t t64_now;
|
||||||
|
@ -510,6 +513,9 @@ video_h264_encoder_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *mmalbuf)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vcb->t_cur = pikrellcam.t_now;
|
||||||
|
audio_head = acb->head;
|
||||||
|
|
||||||
if (mmalbuf->pts > 0)
|
if (mmalbuf->pts > 0)
|
||||||
{
|
{
|
||||||
if (pikrellcam.t_now > tv.tv_sec + 10)
|
if (pikrellcam.t_now > tv.tv_sec + 10)
|
||||||
|
@ -549,9 +555,9 @@ video_h264_encoder_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *mmalbuf)
|
||||||
MMAL_PARAMETER_VIDEO_REQUEST_I_FRAME, 1);
|
MMAL_PARAMETER_VIDEO_REQUEST_I_FRAME, 1);
|
||||||
}
|
}
|
||||||
if ( (mmalbuf->flags & MMAL_BUFFER_HEADER_FLAG_KEYFRAME)
|
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);
|
pthread_mutex_lock(&vcb->mutex);
|
||||||
|
@ -585,18 +591,21 @@ video_h264_encoder_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *mmalbuf)
|
||||||
vcb->in_keyframe = TRUE;
|
vcb->in_keyframe = TRUE;
|
||||||
vcb->cur_frame_index = (vcb->cur_frame_index + 1) % KEYFRAME_SIZE;
|
vcb->cur_frame_index = (vcb->cur_frame_index + 1) % KEYFRAME_SIZE;
|
||||||
kf = &vcb->key_frame[vcb->cur_frame_index];
|
kf = &vcb->key_frame[vcb->cur_frame_index];
|
||||||
|
|
||||||
kf->position = vcb->head;
|
kf->position = vcb->head;
|
||||||
|
kf->audio_position = audio_head;
|
||||||
kf->frame_count = 0;
|
kf->frame_count = 0;
|
||||||
pause_frame_count_adjust = 0;
|
pause_frame_count_adjust = 0;
|
||||||
|
|
||||||
if (vcb->pause && vcb->state == VCB_STATE_MANUAL_RECORD)
|
if (vcb->pause && vcb->state == VCB_STATE_MANUAL_RECORD)
|
||||||
{
|
{
|
||||||
vcb->tail = vcb->head;
|
vcb->tail = vcb->head;
|
||||||
|
audio_buffer_set_record_head_tail(acb, audio_head, audio_head);
|
||||||
pts_prev = mmalbuf->pts;
|
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;
|
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)
|
> pikrellcam.motion_times.pre_capture)
|
||||||
{
|
{
|
||||||
vcb->pre_frame_index = (vcb->pre_frame_index + 1) % KEYFRAME_SIZE;
|
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)
|
if (!vcb->pause)
|
||||||
++vcb->record_elapsed_time;
|
++vcb->record_elapsed_time;
|
||||||
t_prev = t_cur;
|
t_prev = vcb->t_cur;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vcb->state == VCB_STATE_MOTION_RECORD_START)
|
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_header_size = vcb->h264_header_position;
|
||||||
pikrellcam.video_size = vcb->h264_header_position;
|
pikrellcam.video_size = vcb->h264_header_position;
|
||||||
|
|
||||||
vcb->tail = vcb->key_frame[vcb->record_start_frame_index].position;
|
kf = &vcb->key_frame[vcb->record_start_frame_index];
|
||||||
vcb_video_write(vcb);
|
vcb->tail = kf->position;
|
||||||
vcb->frame_count = vcb->key_frame[vcb->record_start_frame_index].frame_count;
|
video_buffer_write(vcb);
|
||||||
|
|
||||||
|
vcb->frame_count = kf->frame_count;
|
||||||
vcb->video_frame_count = vcb->frame_count;
|
vcb->video_frame_count = vcb->frame_count;
|
||||||
motion_event_write(vcb, mf);
|
motion_event_write(vcb, mf);
|
||||||
vcb->state = VCB_STATE_MOTION_RECORD;
|
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)
|
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;
|
vcb->max_record_time = mf->external_trigger_time_limit;
|
||||||
}
|
}
|
||||||
else
|
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;
|
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_header_size = vcb->h264_header_position;
|
||||||
pikrellcam.video_size = vcb->h264_header_position;
|
pikrellcam.video_size = vcb->h264_header_position;
|
||||||
|
|
||||||
vcb->tail = vcb->key_frame[vcb->record_start_frame_index].position;
|
kf = &vcb->key_frame[vcb->record_start_frame_index];
|
||||||
|
vcb->tail = kf->position;
|
||||||
vcb_video_write(vcb);
|
video_buffer_write(vcb);
|
||||||
vcb->frame_count = vcb->key_frame[vcb->record_start_frame_index].frame_count;
|
vcb->frame_count = kf->frame_count;
|
||||||
pts_prev = 0;
|
|
||||||
vcb->state = VCB_STATE_MANUAL_RECORD;
|
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;
|
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;
|
vcb->last_pts = mmalbuf->pts;
|
||||||
pts_prev = mmalbuf->pts;
|
pts_prev = mmalbuf->pts;
|
||||||
}
|
}
|
||||||
|
i = 0;
|
||||||
if (prev_pause)
|
if (prev_pause)
|
||||||
{
|
{
|
||||||
vcb->frame_count += pause_frame_count_adjust;
|
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;
|
pause_frame_count_adjust = 0;
|
||||||
}
|
}
|
||||||
vcb->video_frame_count = vcb->frame_count;
|
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;
|
prev_pause = vcb->pause;
|
||||||
|
|
||||||
if (force_stop)
|
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
|
| reached and we stop recording with the post_capture time
|
||||||
| already written.
|
| already written.
|
||||||
*/
|
*/
|
||||||
if (t_cur <= vcb->motion_sync_time)
|
if (vcb->t_cur <= vcb->motion_sync_time)
|
||||||
{
|
{
|
||||||
if (mmalbuf->pts > 0)
|
if (mmalbuf->pts > 0)
|
||||||
vcb->last_pts = mmalbuf->pts;
|
vcb->last_pts = mmalbuf->pts;
|
||||||
vcb->video_frame_count = vcb->frame_count;
|
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
|
if ( force_stop
|
||||||
|| ( mf->external_trigger_time_limit == 0
|
|| ( 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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|
41
src/motion.c
|
@ -1313,24 +1313,43 @@ motion_command(char *cmd_line)
|
||||||
|
|
||||||
case PRE_CAPTURE:
|
case PRE_CAPTURE:
|
||||||
pthread_mutex_lock(&vcb->mutex);
|
pthread_mutex_lock(&vcb->mutex);
|
||||||
n = atoi(arg1);
|
if (vcb->state == VCB_STATE_NONE)
|
||||||
pikrellcam.motion_times.pre_capture = n;
|
{
|
||||||
pthread_mutex_lock(&vcb->mutex);
|
n = atoi(arg1);
|
||||||
motion_times_temp.pre_capture = n;
|
pikrellcam.motion_times.pre_capture = n;
|
||||||
circular_buffer_init();
|
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);
|
pthread_mutex_unlock(&vcb->mutex);
|
||||||
pikrellcam.config_modified = TRUE;
|
|
||||||
log_printf("command process: motion %s\n", cmd_line);
|
log_printf("command process: motion %s\n", cmd_line);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EVENT_GAP:
|
case EVENT_GAP:
|
||||||
n = atoi(arg1);
|
|
||||||
pikrellcam.motion_times.event_gap = n;
|
|
||||||
pthread_mutex_lock(&vcb->mutex);
|
pthread_mutex_lock(&vcb->mutex);
|
||||||
motion_times_temp.event_gap = n;
|
if (vcb->state == VCB_STATE_NONE)
|
||||||
circular_buffer_init();
|
{
|
||||||
|
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);
|
pthread_mutex_unlock(&vcb->mutex);
|
||||||
pikrellcam.config_modified = TRUE;
|
|
||||||
log_printf("command process: motion %s\n", cmd_line);
|
log_printf("command process: motion %s\n", cmd_line);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
262
src/pikrellcam.c
|
@ -1,6 +1,6 @@
|
||||||
/* PiKrellCam
|
/* 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
|
| PiKrellCam is free software: you can redistribute it and/or modify it
|
||||||
| under the terms of the GNU General Public License as published by
|
| under the terms of the GNU General Public License as published by
|
||||||
|
@ -193,7 +193,8 @@ camera_start(void)
|
||||||
char *cmd;
|
char *cmd;
|
||||||
|
|
||||||
motion_init();
|
motion_init();
|
||||||
circular_buffer_init();
|
video_circular_buffer_init();
|
||||||
|
audio_circular_buffer_init();
|
||||||
|
|
||||||
if (!camera_create())
|
if (!camera_create())
|
||||||
{
|
{
|
||||||
|
@ -448,7 +449,6 @@ void
|
||||||
video_record_start(VideoCircularBuffer *vcb, int start_state)
|
video_record_start(VideoCircularBuffer *vcb, int start_state)
|
||||||
{
|
{
|
||||||
MotionFrame *mf = &motion_frame;
|
MotionFrame *mf = &motion_frame;
|
||||||
time_t t_cur = pikrellcam.t_now;
|
|
||||||
int n;
|
int n;
|
||||||
char *s, *path, *stats_path = NULL, seq_buf[12];
|
char *s, *path, *stats_path = NULL, seq_buf[12];
|
||||||
boolean do_stats = FALSE;
|
boolean do_stats = FALSE;
|
||||||
|
@ -463,7 +463,7 @@ video_record_start(VideoCircularBuffer *vcb, int start_state)
|
||||||
n = vcb->cur_frame_index;
|
n = vcb->cur_frame_index;
|
||||||
if (mf->external_trigger_pre_capture > vcb->seconds - 1)
|
if (mf->external_trigger_pre_capture > vcb->seconds - 1)
|
||||||
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)
|
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)
|
if (n == vcb->cur_frame_index)
|
||||||
break;
|
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;
|
n = (n + 1) % KEYFRAME_SIZE;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
n = vcb->pre_frame_index;
|
n = vcb->pre_frame_index;
|
||||||
|
|
||||||
vcb->record_start_time = vcb->key_frame[n].t_frame;
|
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;
|
vcb->record_start_frame_index = n;
|
||||||
pikrellcam.video_start_pts = vcb->key_frame[n].frame_pts;
|
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;
|
n = vcb->cur_frame_index;
|
||||||
if (vcb->manual_pre_capture > vcb->seconds - 1)
|
if (vcb->manual_pre_capture > vcb->seconds - 1)
|
||||||
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)
|
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)
|
if (n == vcb->cur_frame_index)
|
||||||
break;
|
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;
|
n = (n + 1) % KEYFRAME_SIZE;
|
||||||
|
|
||||||
vcb->record_start_time = vcb->key_frame[n].t_frame;
|
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;
|
vcb->record_start_frame_index = n;
|
||||||
pikrellcam.video_start_pts = vcb->key_frame[n].frame_pts;
|
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",
|
snprintf(seq_buf, sizeof(seq_buf), "%d",
|
||||||
pikrellcam.video_manual_sequence);
|
pikrellcam.video_manual_sequence);
|
||||||
path = media_pathname(pikrellcam.video_dir,
|
path = media_pathname(pikrellcam.video_dir,
|
||||||
|
@ -536,6 +539,10 @@ video_record_start(VideoCircularBuffer *vcb, int start_state)
|
||||||
free(path);
|
free(path);
|
||||||
path = pikrellcam.video_pathname;
|
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 ((s = strstr(path, ".mp4")) != NULL && *(s + 4) == '\0')
|
||||||
{
|
{
|
||||||
if (do_stats)
|
if (do_stats)
|
||||||
|
@ -544,6 +551,18 @@ video_record_start(VideoCircularBuffer *vcb, int start_state)
|
||||||
asprintf(&stats_path, "%s.csv", path);
|
asprintf(&stats_path, "%s.csv", path);
|
||||||
*s = '.';
|
*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);
|
asprintf(&path, "%s.h264", pikrellcam.video_pathname);
|
||||||
dup_string(&pikrellcam.video_h264, path);
|
dup_string(&pikrellcam.video_h264, path);
|
||||||
free(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);
|
log_printf("Could not create video file %s. %m\n", path);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
log_printf("Video record: %s ...\n", path);
|
|
||||||
vcb->state = start_state;
|
vcb->state = start_state;
|
||||||
pikrellcam.state_modified = TRUE;
|
pikrellcam.state_modified = TRUE;
|
||||||
|
log_printf("%s record start - %s\n",
|
||||||
|
(start_state == VCB_STATE_MOTION_RECORD_START) ? "Motion" : "Manual",
|
||||||
|
path);
|
||||||
if ( do_stats
|
if ( do_stats
|
||||||
&& (vcb->motion_stats_file = fopen(stats_path, "w")) != NULL
|
&& (vcb->motion_stats_file = fopen(stats_path, "w")) != NULL
|
||||||
)
|
)
|
||||||
|
@ -579,20 +600,53 @@ video_record_stop(VideoCircularBuffer *vcb)
|
||||||
Event *event = NULL;
|
Event *event = NULL;
|
||||||
char *s, *cmd, *tmp_dir, *detect, *thumb_name, *thumb_cmd = NULL;
|
char *s, *cmd, *tmp_dir, *detect, *thumb_name, *thumb_cmd = NULL;
|
||||||
int thumb_height;
|
int thumb_height;
|
||||||
|
double ftmp, encode_fps;
|
||||||
|
|
||||||
if (!vcb->file)
|
if (!vcb->file)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
fclose(vcb->file);
|
fclose(vcb->file);
|
||||||
vcb->file = NULL;
|
vcb->file = NULL;
|
||||||
|
|
||||||
if (vcb->motion_stats_file)
|
if (vcb->motion_stats_file)
|
||||||
{
|
{
|
||||||
fclose(vcb->motion_stats_file);
|
fclose(vcb->motion_stats_file);
|
||||||
vcb->motion_stats_file = NULL;
|
vcb->motion_stats_file = NULL;
|
||||||
}
|
}
|
||||||
log_printf("Video %s record stopped. Header size: %d h264 file size: %d\n",
|
if (pikrellcam.verbose_motion && !pikrellcam.verbose)
|
||||||
(vcb->state & VCB_STATE_MOTION_RECORD) ? "motion" : "manual",
|
printf("***Motion record stop: %s\n", pikrellcam.video_pathname);
|
||||||
pikrellcam.video_header_size, pikrellcam.video_size);
|
|
||||||
|
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 (vcb->state & VCB_STATE_MOTION_RECORD)
|
||||||
{
|
{
|
||||||
if ((mf->first_detect & (MOTION_BURST | MOTION_DIRECTION))
|
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);
|
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;
|
pikrellcam.state_modified = TRUE;
|
||||||
stat(pikrellcam.video_h264, &st_h264);
|
|
||||||
|
|
||||||
if (pikrellcam.video_mp4box)
|
if (pikrellcam.video_mp4box)
|
||||||
{
|
{
|
||||||
|
@ -633,23 +684,49 @@ video_record_stop(VideoCircularBuffer *vcb)
|
||||||
if ((s = strstr(thumb_name, ".mp4")) != NULL)
|
if ((s = strstr(thumb_name, ".mp4")) != NULL)
|
||||||
strcpy(s, ".th.jpg");
|
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,
|
pikrellcam.video_pathname, thumb_height,
|
||||||
thumb_name);
|
thumb_name);
|
||||||
free(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
|
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",
|
if (pikrellcam.audio_pathname)
|
||||||
tmp_dir,
|
{
|
||||||
(pikrellcam.camera_adjust.video_mp4box_fps > 0) ?
|
asprintf(&cmd, "(MP4Box %s -tmp %s -fps %.3f -add %s -add %s %s %s && rm %s %s %s)",
|
||||||
pikrellcam.camera_adjust.video_mp4box_fps :
|
pikrellcam.verbose ? "" : "-quiet",
|
||||||
pikrellcam.camera_adjust.video_fps,
|
tmp_dir,
|
||||||
pikrellcam.video_h264, pikrellcam.video_pathname,
|
(float) encode_fps,
|
||||||
pikrellcam.verbose ? "" : "2> /dev/null",
|
pikrellcam.video_h264,
|
||||||
pikrellcam.video_h264,
|
pikrellcam.audio_pathname,
|
||||||
thumb_cmd ? thumb_cmd : "");
|
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
|
else
|
||||||
asprintf(&cmd, "rm %s", pikrellcam.video_h264);
|
asprintf(&cmd, "rm %s", pikrellcam.video_h264);
|
||||||
|
|
||||||
|
@ -666,10 +743,7 @@ video_record_stop(VideoCircularBuffer *vcb)
|
||||||
free(cmd);
|
free(cmd);
|
||||||
}
|
}
|
||||||
dup_string(&pikrellcam.video_last, pikrellcam.video_pathname);
|
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;
|
pikrellcam.video_notify = TRUE;
|
||||||
event_count_down_add("video saved notify",
|
event_count_down_add("video saved notify",
|
||||||
|
@ -710,41 +784,56 @@ video_record_stop(VideoCircularBuffer *vcb)
|
||||||
vcb->pause = FALSE;
|
vcb->pause = FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean
|
static int
|
||||||
get_arg_pass1(char *arg)
|
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;
|
quit_flag = TRUE;
|
||||||
|
|
||||||
if (!strcmp(arg, "-V") || !strcmp(arg, "--version"))
|
if (!strcmp(opt, "-V") || !strcmp(opt, "--version"))
|
||||||
{
|
{
|
||||||
printf("%s\n", PIKRELLCAM_VERSION);
|
printf("%s\n", PIKRELLCAM_VERSION);
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
else if (!strcmp(arg, "-h") || !strcmp(arg, "--help"))
|
else if (!strcmp(opt, "-h") || !strcmp(opt, "--help"))
|
||||||
{
|
{
|
||||||
/* XXX */
|
/* XXX */
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
else if (!strcmp(arg, "-v"))
|
|
||||||
|
debug_arg = TRUE;
|
||||||
|
if (!strcmp(opt, "-v"))
|
||||||
pikrellcam.verbose = TRUE;
|
pikrellcam.verbose = TRUE;
|
||||||
else if (!strcmp(arg, "-vm"))
|
else if (!strcmp(opt, "-vm"))
|
||||||
pikrellcam.verbose_motion = TRUE;
|
pikrellcam.verbose_motion = TRUE;
|
||||||
else if (!strcmp(arg, "-vmulti"))
|
else if (!strcmp(opt, "-vmulti"))
|
||||||
pikrellcam.verbose_multicast = TRUE;
|
pikrellcam.verbose_multicast = TRUE;
|
||||||
else if (!strcmp(arg, "-debug"))
|
else if (!strcmp(opt, "-debug"))
|
||||||
pikrellcam.debug = TRUE;
|
pikrellcam.debug = TRUE;
|
||||||
else if (!strcmp(arg, "-debug-fps"))
|
else if (!strcmp(opt, "-debug-fps"))
|
||||||
pikrellcam.debug_fps = TRUE;
|
pikrellcam.debug_fps = TRUE;
|
||||||
else if (!strncmp(arg, "-user", 5))
|
else if (!strcmp(opt, "-ad"))
|
||||||
user_uid = atoi(arg + 5);
|
{
|
||||||
else if (!strncmp(arg, "-group", 6))
|
pikrellcam.audio_debug = atoi(arg);
|
||||||
user_gid = atoi(arg + 6);
|
args_used = 2;
|
||||||
else if (!strncmp(arg, "-home", 5))
|
}
|
||||||
homedir = strdup(arg + 5);
|
|
||||||
else
|
else
|
||||||
return FALSE;
|
debug_arg = FALSE;
|
||||||
return TRUE;
|
|
||||||
|
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
|
static void
|
||||||
|
@ -796,6 +885,7 @@ typedef enum
|
||||||
annotate_string,
|
annotate_string,
|
||||||
delete_log,
|
delete_log,
|
||||||
fix_thumbs,
|
fix_thumbs,
|
||||||
|
audio_cmd,
|
||||||
servo_cmd,
|
servo_cmd,
|
||||||
preset_cmd,
|
preset_cmd,
|
||||||
multicast,
|
multicast,
|
||||||
|
@ -849,6 +939,7 @@ static Command commands[] =
|
||||||
{ "delete_log", delete_log, 0, TRUE },
|
{ "delete_log", delete_log, 0, TRUE },
|
||||||
{ "fix_thumbs", fix_thumbs, 1, TRUE },
|
{ "fix_thumbs", fix_thumbs, 1, TRUE },
|
||||||
{ "annotate_string", annotate_string, 1, FALSE },
|
{ "annotate_string", annotate_string, 1, FALSE },
|
||||||
|
{ "audio", audio_cmd, 1, FALSE },
|
||||||
{ "preset", preset_cmd, 1, FALSE },
|
{ "preset", preset_cmd, 1, FALSE },
|
||||||
{ "servo", servo_cmd, 1, FALSE },
|
{ "servo", servo_cmd, 1, FALSE },
|
||||||
{ "multicast", multicast, 1, TRUE },
|
{ "multicast", multicast, 1, TRUE },
|
||||||
|
@ -1214,6 +1305,10 @@ command_process(char *command_line)
|
||||||
log_printf("Wrong number of args for command: %s\n", command);
|
log_printf("Wrong number of args for command: %s\n", command);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case audio_cmd:
|
||||||
|
audio_command(args);
|
||||||
|
break;
|
||||||
|
|
||||||
case preset_cmd:
|
case preset_cmd:
|
||||||
preset_command(args);
|
preset_command(args);
|
||||||
break;
|
break;
|
||||||
|
@ -1410,7 +1505,11 @@ make_dir(char *dir)
|
||||||
log_printf_no_timestamp(" make_dir() execing sudo mkdir -p %s\n", dir);
|
log_printf_no_timestamp(" make_dir() execing sudo mkdir -p %s\n", dir);
|
||||||
exec_wait("sudo mkdir -p $F", dir);
|
exec_wait("sudo mkdir -p $F", dir);
|
||||||
if ((dir_exists = isdir(dir)) == FALSE)
|
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
|
else
|
||||||
{
|
{
|
||||||
if (pikrellcam.verbose)
|
if (pikrellcam.verbose)
|
||||||
|
@ -1442,6 +1541,35 @@ log_start(boolean start_sep, boolean time, boolean end_sep)
|
||||||
log_printf_no_timestamp("========================================================\n");
|
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
|
int
|
||||||
main(int argc, char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
@ -1457,9 +1585,11 @@ main(int argc, char *argv[])
|
||||||
|
|
||||||
time(&pikrellcam.t_now);
|
time(&pikrellcam.t_now);
|
||||||
|
|
||||||
for (i = 1; i < argc; i++)
|
for (i = 1; i < argc; )
|
||||||
get_arg_pass1(argv[i]);
|
{
|
||||||
|
n = get_arg_pass1(argv[i], argv[i+1]);
|
||||||
|
i += (n > 0) ? n : 1;
|
||||||
|
}
|
||||||
config_set_defaults(homedir);
|
config_set_defaults(homedir);
|
||||||
|
|
||||||
if (!config_load(pikrellcam.config_file))
|
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);
|
log_printf("setting LC_TIME to %s\n", pikrellcam.lc_time);
|
||||||
setlocale(LC_TIME, 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
|
/* 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
|
| 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)
|
if (user_gid > 0)
|
||||||
setgid(user_gid);
|
setgid(user_gid);
|
||||||
setuid(user_uid);
|
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_printf_no_timestamp("== Dropped root priviledges-continuing as normal user ==\n");
|
||||||
log_start(FALSE, FALSE, TRUE);
|
log_start(FALSE, FALSE, TRUE);
|
||||||
}
|
}
|
||||||
|
@ -1507,7 +1640,7 @@ main(int argc, char *argv[])
|
||||||
i += sprintf(buf + i, "%s ", *argv++);
|
i += sprintf(buf + i, "%s ", *argv++);
|
||||||
|
|
||||||
set_exec_with_session(FALSE);
|
set_exec_with_session(FALSE);
|
||||||
exec_wait(buf, NULL); /* restart as root so can mmap() gpios*/
|
exec_wait(buf, NULL);
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
else if (pikrellcam.servo_use_servoblaster)
|
else if (pikrellcam.servo_use_servoblaster)
|
||||||
|
@ -1540,8 +1673,11 @@ main(int argc, char *argv[])
|
||||||
|
|
||||||
for (i = 1; i < argc; i++)
|
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;
|
continue;
|
||||||
|
}
|
||||||
opt = argv[i];
|
opt = argv[i];
|
||||||
|
|
||||||
/* Accept: --opt arg -opt arg opt=arg --opt=arg -opt=arg
|
/* Accept: --opt arg -opt arg opt=arg --opt=arg -opt=arg
|
||||||
|
@ -1593,13 +1729,15 @@ main(int argc, char *argv[])
|
||||||
check_modes(buf, 0775);
|
check_modes(buf, 0775);
|
||||||
|
|
||||||
asprintf(&pikrellcam.command_fifo, "%s/www/FIFO", pikrellcam.install_dir);
|
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_dir, "%s/scripts", pikrellcam.install_dir);
|
||||||
asprintf(&pikrellcam.scripts_dist_dir, "%s/scripts-dist", 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.mjpeg_filename, "%s/mjpeg.jpg", pikrellcam.tmpfs_dir);
|
||||||
asprintf(&pikrellcam.state_filename, "%s/state", 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("command FIFO: %s\n", pikrellcam.command_fifo);
|
||||||
log_printf_no_timestamp("using mjpeg: %s\n", pikrellcam.mjpeg_filename);
|
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
|
/* Subdirs must match www/config.php and the init script is supposed
|
||||||
|
@ -1645,6 +1783,9 @@ main(int argc, char *argv[])
|
||||||
exit(1);
|
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)
|
if ((fifo = open(pikrellcam.command_fifo, O_RDONLY | O_NONBLOCK)) < 0)
|
||||||
{
|
{
|
||||||
log_printf("Failed to open FIFO: %s. %m\n", pikrellcam.command_fifo);
|
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));
|
read(fifo, buf, sizeof(buf));
|
||||||
|
|
||||||
camera_start();
|
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();
|
config_timelapse_load_status();
|
||||||
preset_state_load();
|
preset_state_load();
|
||||||
pikrellcam.state_modified = TRUE;
|
pikrellcam.state_modified = TRUE;
|
||||||
|
@ -1662,6 +1809,7 @@ main(int argc, char *argv[])
|
||||||
signal(SIGINT, signal_quit);
|
signal(SIGINT, signal_quit);
|
||||||
signal(SIGTERM, signal_quit);
|
signal(SIGTERM, signal_quit);
|
||||||
signal(SIGCHLD, event_child_signal);
|
signal(SIGCHLD, event_child_signal);
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
|
||||||
setup_h264_tcp_server();
|
setup_h264_tcp_server();
|
||||||
setup_mjpeg_tcp_server();
|
setup_mjpeg_tcp_server();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/* PiKrellCam
|
/* 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
|
| PiKrellCam is free software: you can redistribute it and/or modify it
|
||||||
| under the terms of the GNU General Public License as published by
|
| under the terms of the GNU General Public License as published by
|
||||||
|
@ -34,6 +34,7 @@
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <grp.h>
|
#include <grp.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
@ -48,9 +49,12 @@
|
||||||
#include "interface/mmal/util/mmal_default_components.h"
|
#include "interface/mmal/util/mmal_default_components.h"
|
||||||
#include "interface/mmal/util/mmal_connection.h"
|
#include "interface/mmal/util/mmal_connection.h"
|
||||||
|
|
||||||
|
#include <alsa/asoundlib.h>
|
||||||
|
#include <lame/lame.h>
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
#define PIKRELLCAM_VERSION "3.1.4"
|
#define PIKRELLCAM_VERSION "4.0.0"
|
||||||
|
|
||||||
|
|
||||||
//TCP Stream Server
|
//TCP Stream Server
|
||||||
|
@ -344,6 +348,8 @@ typedef struct
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
int position;
|
int position;
|
||||||
|
int audio_position;
|
||||||
|
|
||||||
time_t t_frame;
|
time_t t_frame;
|
||||||
int frame_count;
|
int frame_count;
|
||||||
uint64_t frame_pts;
|
uint64_t frame_pts;
|
||||||
|
@ -365,6 +371,7 @@ typedef struct
|
||||||
{
|
{
|
||||||
pthread_mutex_t mutex;
|
pthread_mutex_t mutex;
|
||||||
|
|
||||||
|
time_t t_cur;
|
||||||
FILE *file,
|
FILE *file,
|
||||||
*motion_stats_file;
|
*motion_stats_file;
|
||||||
boolean motion_stats_do_header;
|
boolean motion_stats_do_header;
|
||||||
|
@ -377,7 +384,7 @@ typedef struct
|
||||||
|
|
||||||
int8_t *data; /* h.264 video data array */
|
int8_t *data; /* h.264 video data array */
|
||||||
int size; /* size in bytes of 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,
|
int head,
|
||||||
tail;
|
tail;
|
||||||
|
|
||||||
|
@ -402,6 +409,54 @@ typedef struct
|
||||||
VideoCircularBuffer;
|
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 -----------
|
/* -------------- The Global PiKrellCam Environment -----------
|
||||||
*/
|
*/
|
||||||
typedef struct
|
typedef struct
|
||||||
|
@ -467,6 +522,7 @@ typedef struct
|
||||||
t_start;
|
t_start;
|
||||||
struct tm tm_local;
|
struct tm tm_local;
|
||||||
int second_tick;
|
int second_tick;
|
||||||
|
int pi_model;
|
||||||
|
|
||||||
char *install_dir,
|
char *install_dir,
|
||||||
*version,
|
*version,
|
||||||
|
@ -545,8 +601,13 @@ typedef struct
|
||||||
int video_manual_sequence,
|
int video_manual_sequence,
|
||||||
video_motion_sequence,
|
video_motion_sequence,
|
||||||
video_header_size,
|
video_header_size,
|
||||||
video_size,
|
video_size;
|
||||||
video_last_frame_count;
|
|
||||||
|
int video_last_frame_count,
|
||||||
|
audio_last_frame_count,
|
||||||
|
audio_last_rate;
|
||||||
|
double video_last_time,
|
||||||
|
video_last_fps;
|
||||||
uint64_t video_start_pts,
|
uint64_t video_start_pts,
|
||||||
video_end_pts;
|
video_end_pts;
|
||||||
|
|
||||||
|
@ -591,6 +652,18 @@ typedef struct
|
||||||
servo_use_servoblaster,
|
servo_use_servoblaster,
|
||||||
have_servos;
|
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;
|
SList *preset_position_list;
|
||||||
int preset_position_index,
|
int preset_position_index,
|
||||||
n_preset_positions;
|
n_preset_positions;
|
||||||
|
@ -722,6 +795,7 @@ extern CameraObject stream_splitter;
|
||||||
extern CameraObject stream_resizer;
|
extern CameraObject stream_resizer;
|
||||||
|
|
||||||
extern VideoCircularBuffer video_circular_buffer;
|
extern VideoCircularBuffer video_circular_buffer;
|
||||||
|
extern AudioCircularBuffer audio_circular_buffer;
|
||||||
extern MotionFrame motion_frame;
|
extern MotionFrame motion_frame;
|
||||||
extern TimeLapse time_lapse;
|
extern TimeLapse time_lapse;
|
||||||
|
|
||||||
|
@ -755,7 +829,7 @@ void video_h264_encoder_callback(MMAL_PORT_T *port,
|
||||||
MMAL_BUFFER_HEADER_T *mmalbuf);
|
MMAL_BUFFER_HEADER_T *mmalbuf);
|
||||||
boolean camera_create(void);
|
boolean camera_create(void);
|
||||||
void camera_object_destroy(CameraObject *obj);
|
void camera_object_destroy(CameraObject *obj);
|
||||||
void circular_buffer_init(void);
|
void video_circular_buffer_init(void);
|
||||||
|
|
||||||
void mmalcam_config_parameters_set_camera(void);
|
void mmalcam_config_parameters_set_camera(void);
|
||||||
boolean mmalcam_config_parameter_set(char *name, char *value, boolean set_camera);
|
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_command(char *args);
|
||||||
void servo_init(void);
|
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 set_exec_with_session(boolean set);
|
||||||
void sun_times_init(void);
|
void sun_times_init(void);
|
||||||
void at_commands_config_save(char *config_file);
|
void at_commands_config_save(char *config_file);
|
||||||
|
|
31
src/servo.c
|
@ -269,34 +269,6 @@ gpio_write(int pin, int level)
|
||||||
*(gpio_mmap + reg) = 1 << (pin & 0x1f);
|
*(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
|
static void
|
||||||
_servo_move(int pan, int tilt, int delay)
|
_servo_move(int pan, int tilt, int delay)
|
||||||
{
|
{
|
||||||
|
@ -454,7 +426,8 @@ servo_init(void)
|
||||||
pan_channel = tilt_channel = -1;
|
pan_channel = tilt_channel = -1;
|
||||||
return;
|
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)
|
if ((fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
?>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
// Do not edit this file. Edit config-user.php instead.
|
// Do not edit this file. Edit config-user.php instead.
|
||||||
//
|
//
|
||||||
$config_event_count = 18;
|
$config_event_count = 20;
|
||||||
|
|
||||||
$n_columns = 4;
|
$n_columns = 4;
|
||||||
$name_style = "short";
|
$name_style = "short";
|
||||||
|
@ -26,6 +26,7 @@ $media_thumbs_scrolled = "yes";
|
||||||
$videos_mode = "thumbs";
|
$videos_mode = "thumbs";
|
||||||
|
|
||||||
$video_url = "";
|
$video_url = "";
|
||||||
|
$show_audio_controls = "yes";
|
||||||
$include_control = "no";
|
$include_control = "no";
|
||||||
|
|
||||||
function config_user_save()
|
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 $n_log_scroll_pixels, $log_text_color, $n_thumb_scroll_pixels, $background_image;
|
||||||
global $config_event_count, $include_control;
|
global $config_event_count, $include_control;
|
||||||
global $archive_initial_view, $archive_thumbs_scrolled, $media_thumbs_scrolled, $videos_mode;
|
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");
|
$file = fopen("config-user.php", "w");
|
||||||
if (!$file)
|
if (!$file)
|
||||||
|
@ -88,6 +89,7 @@ function config_user_save()
|
||||||
"// The remaining defines here are changed by web page buttons and should not\n"
|
"// The remaining defines here are changed by web page buttons and should not\n"
|
||||||
."// need to be edited here.\n"
|
."// need to be edited here.\n"
|
||||||
."//\n");
|
."//\n");
|
||||||
|
fwrite($file, "define(\"SHOW_AUDIO_CONTROLS\", \"$show_audio_controls\");\n\n");
|
||||||
fwrite($file, "define(\"NAME_STYLE\", \"$name_style\");\n");
|
fwrite($file, "define(\"NAME_STYLE\", \"$name_style\");\n");
|
||||||
fwrite($file, "define(\"N_COLUMNS\", \"$n_columns\");\n");
|
fwrite($file, "define(\"N_COLUMNS\", \"$n_columns\");\n");
|
||||||
fwrite($file, "define(\"VIDEOS_MODE\", \"$videos_mode\");\n");
|
fwrite($file, "define(\"VIDEOS_MODE\", \"$videos_mode\");\n");
|
||||||
|
@ -151,6 +153,9 @@ if (defined('BACKGROUND_IMAGE'))
|
||||||
if (defined('VIDEO_URL'))
|
if (defined('VIDEO_URL'))
|
||||||
$video_url = VIDEO_URL;
|
$video_url = VIDEO_URL;
|
||||||
|
|
||||||
|
if (defined('SHOW_AUDIO_CONTROLS'))
|
||||||
|
$show_audio_controls = SHOW_AUDIO_CONTROLS;
|
||||||
|
|
||||||
if (defined('INCLUDE_CONTROL'))
|
if (defined('INCLUDE_CONTROL'))
|
||||||
$include_control = INCLUDE_CONTROL;
|
$include_control = INCLUDE_CONTROL;
|
||||||
|
|
||||||
|
|
317
www/help.php
|
@ -65,7 +65,14 @@ Under construction...
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span style='font-size: 1.5em; font-weight: 650;'>Release Notes</span><hr>
|
<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
|
Version 3.1
|
||||||
<div class='indent1'>
|
<div class='indent1'>
|
||||||
<a href="help.php#MULTICAST_INTERFACE">multicast interface</a><br>
|
<a href="help.php#MULTICAST_INTERFACE">multicast interface</a><br>
|
||||||
|
@ -73,41 +80,6 @@ Version 3.1
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</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>
|
<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>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<span style='font-size: 1.5em; font-weight: 650;'>Motion Regions Panel</span><hr>
|
<span style='font-size: 1.5em; font-weight: 650;'>Motion Regions Panel</span><hr>
|
||||||
<img src="images/motion-regions.jpg" alt="motion-regions.jpg">
|
<img src="images/motion-regions.jpg" alt="motion-regions.jpg">
|
||||||
<div class='indent0'>
|
<div class='indent0'>
|
||||||
|
@ -840,6 +814,218 @@ Preset group and there will be no Servo button in the Config group.
|
||||||
|
|
||||||
</div>
|
</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>
|
||||||
|
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>
|
<span style='font-size: 1.5em; font-weight: 650;'>Configuration Files</span><hr>
|
||||||
<div class='indent0'>
|
<div class='indent0'>
|
||||||
<span style='font-size: 1.2em; font-weight: 650;'>~/.pikrellcam/pikrellcam.conf</span>
|
<span style='font-size: 1.2em; font-weight: 650;'>~/.pikrellcam/pikrellcam.conf</span>
|
||||||
|
@ -965,42 +1151,57 @@ archive_dir archive
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
With this setup, all media files are stored on the Pi SD card. Media files may be configured to
|
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:
|
the line:
|
||||||
<pre>
|
<pre>
|
||||||
MOUNT_DISK=sda1
|
MOUNT_DISK=sda1
|
||||||
</pre>
|
</pre>
|
||||||
<p>
|
<p>
|
||||||
This assumes there is a single USB disk plugged into the Pi and it appears as
|
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
|
<nobr>/dev/sda1</nobr>, pikrellcam can create directories with the
|
||||||
needed permissions. However, if the filesystem is not a linux filesystem
|
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
|
(eg. VFAT or FAT32) then pikrellcam cannot set up the needed permissions for
|
||||||
web interface to work and media files will not be shown
|
the web interface to work and media files will not be shown
|
||||||
unless the proper permissions are specified when
|
unless the proper permissions are specified when
|
||||||
the partition is mounted. For this case, the mount command or fstab entry
|
the partition is mounted.
|
||||||
should specify umask or dmask/fmask permissions of 0002. For example,
|
For this case, the mount command or fstab entry
|
||||||
use a mount command like:
|
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>
|
<pre>
|
||||||
sudo mount -t vfat /dev/sda1 /media/mountdir -o rw,user,umask=0002
|
sudo mount -t vfat /dev/sda1 /media/mountdir -o rw,user,umask=0002
|
||||||
</pre>
|
</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>
|
<pre>
|
||||||
/dev/sda1 /media/mountdir vfat rw,user,umask=0002 0 0
|
/dev/sda1 /media/mountdir vfat rw,user,umask=0002 0 0
|
||||||
</pre>
|
</pre>
|
||||||
You can use dmask=0002,fmask=0002 in place of umask=0002.<br>
|
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>
|
<p>
|
||||||
and the media links in <nobr>~/pikrellcam/www</nobr> will now be pointing into the mounted USB disk.
|
If mounting a large CIFS filesystem the nounix,noserverino options may
|
||||||
The media links may be changed to point to some other part of the filesystem which can be
|
be needed in fstab so pikrellcam can make directories. Look at the example
|
||||||
mounted with a USB disk or NAS. Just change the media_dir or archive_dir values in
|
fstab entry forum
|
||||||
pikrellcam.conf to reference an absolute path.
|
<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>
|
</div><br><br>
|
||||||
|
|
||||||
<span style='font-size: 1.5em; font-weight: 500;'>Archiving</span><hr>
|
<span style='font-size: 1.5em; font-weight: 500;'>Archiving</span><hr>
|
||||||
|
@ -1098,6 +1299,12 @@ a communication pipe named
|
||||||
<p>
|
<p>
|
||||||
List of <span style='font-weight:700'>FIFO</span> commands:
|
List of <span style='font-weight:700'>FIFO</span> commands:
|
||||||
<pre>
|
<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
|
||||||
record on pre_capture_time
|
record on pre_capture_time
|
||||||
record on pre_capture_time time_limit
|
record on pre_capture_time time_limit
|
||||||
|
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 111 KiB |
After Width: | Height: | Size: 815 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 856 B |
After Width: | Height: | Size: 771 B |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 852 B |
After Width: | Height: | Size: 864 B |
After Width: | Height: | Size: 822 B |
|
@ -91,7 +91,18 @@ function time_lapse_period()
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<?php
|
<?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-center\">";
|
||||||
echo "<div class='text-shadow-large'>";
|
echo "<div class='text-shadow-large'>";
|
||||||
echo TITLE_STRING;
|
echo TITLE_STRING;
|
||||||
|
@ -102,28 +113,60 @@ echo "<body background=\"$background_image\" onload=\"mjpeg_start();\">";
|
||||||
style=\"border:4px groove silver;\"
|
style=\"border:4px groove silver;\"
|
||||||
onclick=\"image_expand_toggle();\"
|
onclick=\"image_expand_toggle();\"
|
||||||
></div>";
|
></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"
|
<input type="image" src="images/stop.png"
|
||||||
style="vertical-align: bottom;"
|
style="vertical-align: bottom; margin-left:20px;"
|
||||||
onclick="fifo_command('record off')"
|
onclick="fifo_command('record off')"
|
||||||
width="30" height="30"
|
width="28" height="28"
|
||||||
>
|
>
|
||||||
<input type="image" src="images/pause.png"
|
<input type="image" src="images/pause.png"
|
||||||
style="vertical-align: bottom;"
|
style="vertical-align: bottom;"
|
||||||
onclick="fifo_command('pause')"
|
onclick="fifo_command('pause')"
|
||||||
width="30" height="30"
|
width="28" height="28"
|
||||||
>
|
>
|
||||||
<input type="image" src="images/record.png"
|
<input type="image" src="images/record.png"
|
||||||
style="vertical-align: bottom;"
|
style="vertical-align: bottom;"
|
||||||
onclick="fifo_command('record on')"
|
onclick="fifo_command('record on')"
|
||||||
width="30" height="30"
|
width="28" height="28"
|
||||||
>
|
>
|
||||||
<input type="image" src="images/shutter.png"
|
<input type="image" src="images/shutter.png"
|
||||||
width="30" height="30"
|
width="28" height="28"
|
||||||
onclick="fifo_command('still')"
|
onclick="fifo_command('still')"
|
||||||
style="margin-left:16px; vertical-align: bottom;"
|
style="margin-left:12px; vertical-align: bottom;"
|
||||||
>
|
>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
|
@ -611,6 +654,14 @@ if ($servos_enable == "servos_on")
|
||||||
onclick="fifo_command('halt')"
|
onclick="fifo_command('halt')"
|
||||||
class="btn-control alert-control"
|
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>
|
||||||
</div>
|
</div>
|
||||||
<?php
|
<?php
|
||||||
|
|
|
@ -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()
|
function create_XMLHttpRequest()
|
||||||
{
|
{
|
||||||
if (window.XMLHttpRequest)
|
if (window.XMLHttpRequest)
|
||||||
|
|