Skip to content

Commit

Permalink
Improved audio recording reliability
Browse files Browse the repository at this point in the history
  • Loading branch information
connervieira committed Oct 29, 2024
1 parent 6732cfa commit a0c153b
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 19 deletions.
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,9 +473,13 @@ This update emphasizes improving the reliability of Predator, especially when op
- Improved the GPS overlay stamp.
- The GPS query now uses a "lazy" method, which trades data recency for response time.
- This significantly improves frame-rate by reducing the time Predator spends waiting for a GPS response.
- Added configuration option to use a different audio recording device.
- Updated audio recording.
- Improved the reliability of audio recording.
- Fixed unexpected behavior when the working directory path contained spaces.
- Added configuration option to use a different audio recording device.
- Added configuration option to determine which user will be used to run the audio recording process.
- Added per-device resolution configuration.
- Added the ability to disable capture devices without removing them from the configuration entirely.
- Added the ability to disable capture devices in the configuration without removing them from the configuration entirely.
- Predator now changes the status light color when a video is being locked.
- Dashcam video save events can now be triggered using buttons via GPIO pins.
- Improved looped recording.
Expand Down
35 changes: 18 additions & 17 deletions dashcam.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@
audio_recorders = {} # This will hold each audio recorder process.
first_segment_started_time = 0

audio_record_command = "sudo -u pi arecord --format=S16_LE"
audio_record_delay = 0.2 # This is the length of time (in seconds) that Predator will wait before starting the next audio recorder. This prevents the capture device from being opened by two threads at once.
audio_record_command = "sudo -u " + str(config["dashcam"]["capture"]["audio"]["record_as_user"]) + " arecord --format=S16_LE"
if (config["dashcam"]["capture"]["audio"]["device"] != ""): # Check to see if a custom device has been set.
audio_record_command += " --device=" + str(config["dashcam"]["capture"]["audio"]["device"]) + ""

Expand Down Expand Up @@ -193,25 +194,26 @@ def watch_button(pin, hold_time=0.2, event=create_trigger_file):

def run_command_delayed(command, delay=5):
time.sleep(delay)
subprocess.run(command.split(), stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
subprocess.run(command, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)



def merge_audio_video(video_file, audio_file, output_file, audio_offset=0):
debug_message("Merging audio and video files")

merge_command = "ffmpeg -i " + audio_file + " -itsoffset -" + str(audio_offset) + " -i " + video_file + " -c copy " + output_file
erase_command = "timeout 1 rm " + video_file + " " + audio_file
merge_command = ["ffmpeg", "-i", audio_file, "-itsoffset", "-" + str(audio_offset), "-i", video_file, "-c", "copy", output_file]
erase_command = ["timeout", "1", "rm", video_file, audio_file]

merge_process = subprocess.run(merge_command.split(), stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
merge_process = subprocess.run(merge_command, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
first_attempt = utils.get_time()
while (merge_process.returncode != 0): # If the merge process exited with an error, keep trying until it is successful. This might happen if one of the files hasn't fully saved to disk.
merge_process = subprocess.run(merge_command.split(), stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
#merge_process = subprocess.run(merge_command, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
merge_process = subprocess.run(merge_command, stdout=subprocess.DEVNULL) # TODO
if (utils.get_time() - first_attempt > 5): # Check to see if FFMPEG has been trying for at least 5 seconds.
display_message("The audio and video segments could not be merged. It is possible one or both of the files is damaged.", 2)
process_timing("end", "Dashcam/File Merging")
return False # Time out, and exit with a success value False.
delayed_erase_command = threading.Thread(target=run_command_delayed, args=[erase_command, 5])
delayed_erase_command = threading.Thread(target=run_command_delayed, args=[erase_command, 2]) # Create a thread to erase the old files with a delay (so other threads can finish)
delayed_erase_command.start()

debug_message("Merged audio and video files")
Expand Down Expand Up @@ -462,9 +464,9 @@ def record_parked_motion(capture, framerate, width, height, device, directory, f
audio_base_name = "_".join(os.path.basename(current_segment_name[device]).split("_")[0:3])
audio_filepath = directory + "/" + audio_base_name + "." + str(config["dashcam"]["capture"]["audio"]["extension"])
if (audio_base_name not in audio_recorders or audio_recorders[audio_base_name].poll() is not None): # Check to see if the audio recorder hasn't yet been started by another thread.
command = audio_record_command.split(" ")
command.append(audio_filepath)
audio_recorders[audio_base_name] = subprocess.Popen(command, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) # Start the next segment's audio recorder.
subprocess.Popen(("sudo -u " + str(config["dashcam"]["capture"]["audio"]["record_as_user"]) + " killall arecord").split(" "), stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) # Kill the previous arecord instance (if one exists)
command = audio_record_command + " \"" + str(audio_filepath) + "\""
audio_recorders[audio_base_name] = subprocess.Popen(command, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) # Start the next segment's audio recorder.
del command
process_timing("end", "Dashcam/Audio Processing")

Expand Down Expand Up @@ -681,9 +683,8 @@ def capture_dashcam_video(directory, device="main", width=1280, height=720):
audio_base_name = "_".join(os.path.basename(current_segment_name[device]).split("_")[0:3])
audio_filepath = directory + "/" + audio_base_name + "." + str(config["dashcam"]["capture"]["audio"]["extension"])
if (audio_base_name not in audio_recorders or audio_recorders[audio_base_name].poll() is not None): # Check to see if the audio recorder hasn't yet been started by another thread.
command = audio_record_command.split(" ")
command.append(audio_filepath)
audio_recorders[audio_base_name] = subprocess.Popen(command, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) # Start the next segment's audio recorder.
command = "sleep " + str(audio_record_delay) + "; " + audio_record_command + " \"" + str(audio_filepath) + "\""
audio_recorders[audio_base_name] = subprocess.Popen(command, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) # Start the next segment's audio recorder.
del command
process_timing("end", "Dashcam/Audio Processing")

Expand Down Expand Up @@ -715,9 +716,9 @@ def capture_dashcam_video(directory, device="main", width=1280, height=720):
audio_base_name = "_".join(os.path.basename(current_segment_name[device]).split("_")[0:3])
audio_filepath = directory + "/" + audio_base_name + "." + str(config["dashcam"]["capture"]["audio"]["extension"])
if (audio_base_name not in audio_recorders or audio_recorders[audio_base_name].poll() is not None): # Check to see if the audio recorder hasn't yet been started by another thread.
command = audio_record_command.split(" ")
command.append(audio_filepath)
audio_recorders[audio_base_name] = subprocess.Popen(command, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) # Start the next segment's audio recorder.
subprocess.Popen(("sudo -u " + str(config["dashcam"]["capture"]["audio"]["record_as_user"]) + " killall arecord").split(" "), stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) # Kill the previous arecord instance (if one exists)
command = "sleep " + str(audio_record_delay) + "; " + audio_record_command + " \"" + str(audio_filepath) + "\""
audio_recorders[audio_base_name] = subprocess.Popen(command, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) # Start the next segment's audio recorder.
del command
process_timing("end", "Dashcam/Audio Processing")

Expand Down Expand Up @@ -1177,7 +1178,7 @@ def dashcam_output_handler(directory, device, width, height, framerate):
audio_offset = -(config["dashcam"]["parked"]["recording"]["buffer"] / calculated_framerate[device]) # Calculate the audio offset based on the size of the frame-buffer
print("Applying offset of " + str(audio_offset))
else: # Otherwise, the video was recorded during normal operating.
audio_offset = 0 # Don't apply an offset, because the audio and video file should start at the same time.
audio_offset = audio_record_delay # Don't apply an offset, because the audio and video file should start at the same time.
merge_audio_video(last_video_path, last_audio_path, last_filename_merged, audio_offset) # Run the audio/video merge.
process_timing("end", "Dashcam/File Merging")

Expand Down

0 comments on commit a0c153b

Please sign in to comment.