Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ FFF
FFFFFFFFFFFF0102
fffe
fffff
FFmpeg
Fi
filepath
fini
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/examples-linux-arm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
if: github.actor != 'restyled-io[bot]'

container:
image: ghcr.io/project-chip/chip-build-crosscompile:140
image: ghcr.io/project-chip/chip-build-crosscompile:148
volumes:
- "/tmp/bloat_reports:/tmp/bloat_reports"

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/examples-linux-standalone.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
if: github.actor != 'restyled-io[bot]'

container:
image: ghcr.io/project-chip/chip-build:140
image: ghcr.io/project-chip/chip-build:148
volumes:
- "/tmp/bloat_reports:/tmp/bloat_reports"

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/java-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
runs-on: ubuntu-latest

container:
image: ghcr.io/project-chip/chip-build-java:140
image: ghcr.io/project-chip/chip-build-java:148
options: --privileged --sysctl "net.ipv6.conf.all.disable_ipv6=0
net.ipv4.conf.all.forwarding=0 net.ipv6.conf.all.forwarding=0"

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
runs-on: ubuntu-latest

container:
image: ghcr.io/project-chip/chip-build:140
image: ghcr.io/project-chip/chip-build:148
options: >-
--privileged --sysctl "net.ipv6.conf.all.disable_ipv6=0
net.ipv4.conf.all.forwarding=1 net.ipv6.conf.all.forwarding=1"
Expand Down Expand Up @@ -496,7 +496,7 @@ jobs:
runs-on: ubuntu-latest

container:
image: ghcr.io/project-chip/chip-build:140
image: ghcr.io/project-chip/chip-build:148
options: >-
--privileged
--sysctl net.ipv6.conf.all.disable_ipv6=0
Expand Down
14 changes: 14 additions & 0 deletions examples/camera-app/linux/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ config("config") {
"include/clusters/camera-avsettingsuserlevel-mgmt",
"include/media-controller",
"include/transport",
"include/uploader",
"${chip_root}/examples/camera-app/camera-common/include",
"${chip_root}/examples/camera-app/camera-common/include/media-controller",
"${chip_root}/examples/camera-app/camera-common/include/camera-avstream-controller",
Expand All @@ -51,6 +52,14 @@ config("config") {
]
}

pkg_config("ffmpeg") {
packages = [
"libavformat",
"libavcodec",
"libavutil",
]
}

pkg_config("gstreamer") {
packages = [
"gstreamer-1.0",
Expand All @@ -64,6 +73,9 @@ executable("chip-camera-app") {

configs += [ ":gstreamer" ]

configs += [ ":ffmpeg" ]
libs = [ "curl" ]

sources = [
"${chip_root}/examples/camera-app/linux/include/media-controller/default-media-controller.h",
"${chip_root}/examples/camera-app/linux/src/camera-device.cpp",
Expand All @@ -72,6 +84,8 @@ executable("chip-camera-app") {
"${chip_root}/examples/camera-app/linux/src/clusters/chime/chime-manager.cpp",
"${chip_root}/examples/camera-app/linux/src/clusters/webrtc-provider/webrtc-provider-manager.cpp",
"${chip_root}/examples/camera-app/linux/src/media-controller/default-media-controller.cpp",
"${chip_root}/examples/camera-app/linux/src/pushav-clip-recorder.cpp",
"${chip_root}/examples/camera-app/linux/src/uploader/pushav-uploader.cpp",
"${chip_root}/examples/camera-app/linux/src/webrtc-libdatachannel.cpp",
"${chip_root}/examples/camera-app/linux/src/webrtc-transport.cpp",
"include/CHIPProjectAppConfig.h",
Expand Down
15 changes: 10 additions & 5 deletions examples/camera-app/linux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ Linux.

### 1. Prerequisites

Before building, you must install the necessary GStreamer libraries and
development packages, which are used for video processing and streaming.
Before building, you must install the necessary GStreamer, FFmpeg, curl
libraries and development packages, which are used for video processing,
streaming, recording and uploading.

```
sudo apt update
Expand All @@ -25,7 +26,11 @@ sudo apt install \
gstreamer1.0-plugins-bad \
gstreamer1.0-libav \
libgstreamer1.0-dev \
libgstreamer-plugins-base1.0-dev
libgstreamer-plugins-base1.0-dev \
libavcodec-dev \
libavformat-dev \
libavutil-dev \
libcurl4-openssl-dev
```

### 2. Building
Expand Down Expand Up @@ -72,15 +77,15 @@ environment to ensure all dependencies are correct.
1. Pull the Cross-Compilation Docker Image

```
docker pull ghcr.io/project-chip/chip-build-crosscompile:140
docker pull ghcr.io/project-chip/chip-build-crosscompile:148
```

2. Run the Docker Container This command starts an interactive shell inside the
container and mounts your local connectedhomeip repository into the
container's /var/connectedhomeip directory.

```
docker run -it -v ~/connectedhomeip:/var/connectedhomeip ghcr.io/project-chip/chip-build-crosscompile:140 /bin/bash
docker run -it -v ~/connectedhomeip:/var/connectedhomeip ghcr.io/project-chip/chip-build-crosscompile:148 /bin/bash
```

3. Build Inside the Container From within the Docker container's shell, execute
Expand Down
226 changes: 226 additions & 0 deletions examples/camera-app/linux/include/pushav-clip-recorder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
/*
*
* Copyright (c) 2025 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once

#include "pushav-uploader.h"

#include <algorithm>
#include <atomic>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
#include <vector>

// FFmpeg headers
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/error.h>
#include <libavutil/opt.h>
#include <libavutil/timestamp.h>
}

/**
* @struct BufferData
* @brief Contains buffer information for custom IO operations
*/
struct BufferData
{
uint8_t * mPtr; ///< Pointer to buffer data
size_t mSize; ///< Size left in the buffer
};

/**
* @class PushAVClipRecorder
* @brief Manages multimedia clip recording with DASH/CMAF segmentation
*
* Handles video/audio stream processing, packet queueing, and segmented file output
*/
class PushAVClipRecorder
{
public:
/**
* @struct ClipInfoStruct
* @brief Contains clip configuration and runtime state
*/
struct ClipInfoStruct
{
bool mHasVideo; ///< Video recording enabled flag
bool mHasAudio; ///< Audio recording enabled flag
int mClipId; ///< Current clip identifier
uint32_t mMaxClipDuration; ///< Maximum clip duration in seconds
uint16_t mInitialDuration; ///< Initial clip duration in seconds
uint16_t mAugmentationDuration; ///< Duration increment on motion detect
uint16_t mChunkDuration; ///< Segment duration in seconds
uint16_t mBlindDuration; ///< Duration without recording after motion stop
std::string mRecorderId; ///< Unique recorder identifier
std::string mOutputPath; ///< Base output directory path
AVRational mInputTimeBase; ///< Input time base
std::string mUrl; ///< URL for uploading clips;
int mTriggerType; ///< Recording trigger type
std::chrono::steady_clock::time_point activationTime; ///< Time when the recording started
int mPreRollLength; ///< Pre-roll length in seconds
};

/**
* @struct AudioInfoStruct
* @brief Audio stream configuration parameters
*/
struct AudioInfoStruct
{
uint64_t mChannelLayout; ///< Audio channel layout
int mChannels; ///< Number of audio channels
AVCodecID mAudioCodecId; ///< Audio codec identifier
int mSampleRate; ///< Sampling rate in Hz
int mBitRate; ///< Audio bitrate in bps
int64_t mAudioPts; ///< Audio presentation timestamp
int64_t mAudioDts; ///< Audio decoding timestamp
int mAudioStreamIndex; ///< Audio stream index
int mAudioFrameDuration; ///< Audio frame duration in samples
AVRational mAudioTimeBase; ///< Audio time base
};

/**
* @struct VideoInfoStruct
* @brief Video stream configuration parameters
*/
struct VideoInfoStruct
{
AVCodecID mVideoCodecId; ///< Video codec identifier
int64_t mVideoPts; ///< Video presentation timestamp
int64_t mVideoDts; ///< Video decoding timestamp
int mWidth; ///< Video frame width
int mHeight; ///< Video frame height
int mFrameRate; ///< Video frame rate (fps)
int mVideoFrameDuration; ///< Video frame duration (μs)
AVRational mVideoTimeBase; ///< Video time base
int mVideoStreamIndex; ///< Video stream index
uint32_t mBitRate; ///< Video bitrate in bps
};

/// @name Construction/Destruction
/// @{
PushAVClipRecorder(ClipInfoStruct & aClipInfo, AudioInfoStruct & aAudioInfo, VideoInfoStruct & aVideoInfo,
PushAVUploader * aUploader);
~PushAVClipRecorder();
/// @}

/// @name Recording Control
/// @{
void Start();
void Stop();
/// @}

/**
* @brief Enqueues media data for processing
* @param data Raw media data pointer
* @param size Data size in bytes
* @param isVideo True for video data, false for audio
*/
void PushPacket(const char * data, size_t size, bool isVideo);

std::atomic<bool> mDeinitializeRecorder{ false }; ///< Deinitialization flag
ClipInfoStruct mClipInfo; ///< Clip configuration parameters
void SetRecorderStatus(bool status); ///< Sets the recorder status
bool GetRecorderStatus(); ///< Gets the recorder status

private:
long unsigned int kMaxQueueSize = 500; ///< Maximum queue size for media packets
std::atomic<bool> mRunning{ false }; ///< Recording activity flag

/// @name Stream Configuration
/// @{
AudioInfoStruct mAudioInfo; ///< Audio stream parameters
VideoInfoStruct mVideoInfo; ///< Video stream parameters
/// @}

AVFormatContext * mFormatContext;
AVFormatContext * mInputFormatContext;
AVStream * mVideoStream;
AVStream * mAudioStream;
AVCodecContext * mAudioEncoderContext;
std::thread mWorkerThread;
std::mutex mQueueMutex;
std::condition_variable mCondition;

std::queue<AVPacket *> mAudioQueue;
std::queue<AVPacket *> mVideoQueue;

int mAudioFragment = 1;
int mVideoFragment = 1;
int64_t mCurrentClipStartPts = AV_NOPTS_VALUE;
int64_t mFoundFirstIFramePts = -1;
int64_t currentPts = AV_NOPTS_VALUE;
bool mMetadataSet = false;
bool mUploadedInitSegment = false;
bool mUploadMPD = false;

PushAVUploader * mUploader;

/// @name Internal Methods
/// @{
bool FileExists(const std::string & path);
bool CheckAndUploadFile(std::string path);
bool IsH264IFrame(const uint8_t * data, unsigned int length);
AVPacket * CreatePacket(const uint8_t * data, int size, bool isVideo);

/**
* @brief Processes queued packets and writes them to the output file.
* @return Zero if processing was successful, negative otherwise, positive for warnings.
*/
int ProcessBuffersAndWrite();

/**
* @brief Configures the output format context for DASH/CMAF VoD.
*
* @param output_prefix Base path for output files.
* @param init_seg_pattern Pattern for initialization segments.
* @param media_seg_pattern Pattern for media segments.
*/
int SetupOutput(const std::string & outputPrefix, const std::string & initSegPattern, const std::string & mediaSegPattern);

/**
* @brief Starts the clip recording process.
* @return 0 on success, error code otherwise.
*/
int StartClipRecording();

/**
* @brief Adds a video or audio stream to the output context.
*
* @param type The type of stream to add (AVMEDIA_TYPE_VIDEO or AVMEDIA_TYPE_AUDIO).
*/
int AddStreamToOutput(AVMediaType type);

/**
* @brief Cleans up the output context and associated resources.
*/
void CleanupOutput();

/**
* @brief Finalizes the current clip and prepares for a new one.
*
* @param reason Zero for normal clip finalization or Positive number for abrupt finalization
*/
void FinalizeCurrentClip(int reason);
/// @}
};
Loading
Loading