From b23d3283b492e458c585713b94d38852b45e5f85 Mon Sep 17 00:00:00 2001 From: Vladimir Umek Date: Wed, 18 Feb 2026 17:23:01 +0100 Subject: [PATCH 1/3] example: Simplify FVP_Audio example - Move configuration separately (into app_config.h) - Move auxiliary definitions to accompanying header (app_main.h) - Remove thread_stdin and use define to set streaming duration - Add custom ASSERT to check return values - Move stream initialization to init_streams function --- example/FVP_Audio/README.md | 15 +- .../FVP_Audio/project/FVP_Audio.cproject.yml | 3 +- example/FVP_Audio/project/app_config.h | 59 ++++++ example/FVP_Audio/project/app_main.c | 199 ++++++------------ example/FVP_Audio/project/app_main.h | 54 +++++ 5 files changed, 187 insertions(+), 143 deletions(-) create mode 100644 example/FVP_Audio/project/app_config.h create mode 100644 example/FVP_Audio/project/app_main.h diff --git a/example/FVP_Audio/README.md b/example/FVP_Audio/README.md index 70f86fd1..5184e42b 100644 --- a/example/FVP_Audio/README.md +++ b/example/FVP_Audio/README.md @@ -3,7 +3,8 @@ The **FVP Audio** project demonstrates the CMSIS-Driver vStream API for handling audio input and output streams. -It shows how to capture audio samples from an input stream and output them through a audio output stream. +It shows how to capture audio samples from an input stream and output them through +an audio output stream. ## Operation @@ -11,16 +12,24 @@ The application initializes both audio input and output streams and then continu captures audio samples from the input stream and copies them directly to the output stream. +Streaming stops after receiving the `END_OF_STREAM` event or can be configured to +stop after `AUDIO_STREAM_DURATION` seconds. + The example uses CMSIS-RTOS2 for thread management and event-driven processing. ### Configuration -Default configuration expects the following input and output stream format: +Default configuration ([app_config.h](project/app_config.h)) expects the following input +and output stream format: - 16kHz, 16-bit, 2-channels (stereo) -To change default configuration use configuration defines: +To change the default configuration use the following defines: - `AUDIO_CHANNELS` - sets the number of channels in a stream - `AUDIO_SAMPLE_BITS` - sets the number of bits per sample - `AUDIO_SAMPLE_RATE` - sets the number of samples per second + +The default streaming duration is indefinite. To change this, use: + +- `AUDIO_STREAM_DURATION` - sets the streaming duration in seconds (0: stream indefinitely) diff --git a/example/FVP_Audio/project/FVP_Audio.cproject.yml b/example/FVP_Audio/project/FVP_Audio.cproject.yml index 2908b27e..d51459c0 100644 --- a/example/FVP_Audio/project/FVP_Audio.cproject.yml +++ b/example/FVP_Audio/project/FVP_Audio.cproject.yml @@ -28,6 +28,7 @@ project: - group: Application files: - file: app_main.c + - file: app_config.h - group: Documentation files: @@ -42,5 +43,5 @@ project: output: type: - elf - - bin + - hex - map diff --git a/example/FVP_Audio/project/app_config.h b/example/FVP_Audio/project/app_config.h new file mode 100644 index 00000000..c2dc75a2 --- /dev/null +++ b/example/FVP_Audio/project/app_config.h @@ -0,0 +1,59 @@ +/* + * Copyright 2026 Arm Limited and/or its affiliates. + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 + * + * 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. + */ + +#ifndef APP_CONFIG_H_ +#define APP_CONFIG_H_ + +//-------- <<< Use Configuration Wizard in Context Menu >>> -------------------- +//------ With VS Code: Open Preview for Configuration Wizard ------------------- + + +// Stream duration (seconds) +// Defines the streaming duration in seconds. +// Default: 0 (stream indefinitely) +#ifndef AUDIO_STREAM_DURATION +#define AUDIO_STREAM_DURATION 0 +#endif + +// Audio Configuration +// ====================== + +// Number of channels <1=>Mono <2=>Stereo +// Defines the number of audio channels in stream. +// Default: 2 +#ifndef AUDIO_CHANNELS +#define AUDIO_CHANNELS 2 +#endif + +// Number of bits per sample <0=>8 <1=>16 <2=>24 <3=>32 +// Defines number of bits of information in each sample. +// Default: 16 +#ifndef AUDIO_SAMPLE_BITS +#define AUDIO_SAMPLE_BITS 16 +#endif + +// Sample rate <8000=>8 kHz <16000=>16 kHz <44100=>44.1 kHz <48000=>48 kHz +// Defines the number of samples captured per second. +// Default: 16000 +#ifndef AUDIO_SAMPLE_RATE +#define AUDIO_SAMPLE_RATE 16000 +#endif + +// + +#endif /* APP_CONFIG_H_ */ \ No newline at end of file diff --git a/example/FVP_Audio/project/app_main.c b/example/FVP_Audio/project/app_main.c index 8ea02503..1219067e 100644 --- a/example/FVP_Audio/project/app_main.c +++ b/example/FVP_Audio/project/app_main.c @@ -1,5 +1,5 @@ -/*--------------------------------------------------------------------------- - * Copyright (c) 2025 Arm Limited (or its affiliates). All rights reserved. +/* + * Copyright 2025-2026 Arm Limited and/or its affiliates. * * SPDX-License-Identifier: Apache-2.0 * @@ -14,7 +14,7 @@ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - *---------------------------------------------------------------------------*/ + */ #include #include @@ -23,74 +23,22 @@ #include "cmsis_vstream.h" #include "cmsis_os2.h" -#include "main.h" - - -/* Define the number of audio channels in stream (1=Mono, 2=Stereo) */ -#ifndef AUDIO_CHANNELS -#define AUDIO_CHANNELS 2 -#endif - -/* Define the number of bits per sample (8-bit, 16-bit, 24-bit, 32-bit) */ -#ifndef AUDIO_SAMPLE_BITS -#define AUDIO_SAMPLE_BITS 16 -#endif - -/* Define the number of samples per second (8000, 16000, 44100, 48000) */ -#ifndef AUDIO_SAMPLE_RATE -#define AUDIO_SAMPLE_RATE 16000 -#endif - - -/* Define input and output block size */ -#define IN_BLOCK_SIZE (AUDIO_SAMPLE_RATE * (AUDIO_SAMPLE_BITS/8) * AUDIO_CHANNELS) -#define OUT_BLOCK_SIZE (AUDIO_SAMPLE_RATE * (AUDIO_SAMPLE_BITS/8) * AUDIO_CHANNELS) +#include "app_main.h" -/* Define number of blocks in input and output buffers */ -#define IN_BLOCK_CNT (1U) -#define OUT_BLOCK_CNT (1U) - -/* References to the underlying CMSIS vStream driver */ -extern vStreamDriver_t Driver_vStreamAudioIn; -#define vStream_AudioIn (&Driver_vStreamAudioIn) - -extern vStreamDriver_t Driver_vStreamAudioOut; -#define vStream_AudioOut (&Driver_vStreamAudioOut) - -/* Input and output stream buffers */ -uint8_t audio_in_buf[IN_BLOCK_SIZE * IN_BLOCK_CNT] __attribute__((aligned(32))); -uint8_t audio_out_buf[OUT_BLOCK_SIZE * OUT_BLOCK_CNT] __attribute__((aligned(32))); - -/* Thread attributes */ +/* Attributes for the app_main thread */ const osThreadAttr_t thread_attr_main = { .name = "app_main" }; -const osThreadAttr_t thread_attr_stdin = { .name = "stdin" }; -/* Thread IDs */ +/* ID of the app_main thread */ osThreadId_t thread_id_main; -osThreadId_t thread_id_stdin; - -/* Define stream related thread flags */ -#define FRAME_RECEIVED 1 -#define FRAME_SENT 2 -#define END_OF_STREAM 4 -/* - Thread that waits for stdin characters. -*/ -__NO_RETURN void thread_stdin (void *argument) { - int ch; - - printf("Press any key to stop streaming...\n"); +/* Input and output stream buffers */ +uint8_t audio_in_buf[AUDIO_BUF_BLOCK_SIZE * AUDIO_BUF_BLOCK_CNT] __attribute__((aligned(32))); +uint8_t audio_out_buf[AUDIO_BUF_BLOCK_SIZE * AUDIO_BUF_BLOCK_CNT] __attribute__((aligned(32))); - while (1) { - ch = getchar(); - if (ch != EOF) { - /* Signal end of stream */ - osThreadFlagsSet(thread_id_main, END_OF_STREAM); - } - osDelay(100); - } -} +/* Define stream related thread flags */ +#define FRAME_RECEIVED (1U << 0) +#define FRAME_SENT (1U << 1) +#define END_OF_STREAM (1U << 2) /* Input Stream Event Callback @@ -120,80 +68,64 @@ void AudioOut_Event_Callback (uint32_t event) { } /* - Application main thread. + Initialize streams. + + This function initializes the input and output streams, registers the + event callbacks and sets up the stream buffers. */ -__NO_RETURN void app_main_thread (void *argument) { - int32_t rval; - uint32_t flags; - void *in_block; +void init_streams (void) { + int32_t rval; void *out_block; - /* Create stdin thread */ - thread_id_stdin = osThreadNew(thread_stdin, NULL, &thread_attr_stdin); - if (thread_id_stdin == NULL) { - printf("Failed to create stdin thread.\n"); - } - - /* Initialize Audio Input Stream */ + /* Setup input stream */ rval = vStream_AudioIn->Initialize(AudioIn_Event_Callback); - if (rval != VSTREAM_OK) { - printf("Failed to initialize audio input stream.\n"); - for(;;); - } + ASSERT(rval == VSTREAM_OK); - /* Set Input Audio buffer */ - rval = vStream_AudioIn->SetBuf(audio_in_buf, sizeof(audio_in_buf), IN_BLOCK_SIZE); - if (rval != VSTREAM_OK) { - printf("Failed to set audio input buffer.\n"); - for(;;); - } + rval = vStream_AudioIn->SetBuf(audio_in_buf, sizeof(audio_in_buf), AUDIO_BUF_BLOCK_SIZE); + ASSERT(rval == VSTREAM_OK); - /* Initialize Audio Output Stream */ + /* Setup output stream */ rval = vStream_AudioOut->Initialize(AudioOut_Event_Callback); - if (rval != VSTREAM_OK) { - printf("Failed to initialize audio output stream.\n"); - for(;;); - } + ASSERT(rval == VSTREAM_OK); - /* Set Output Audio buffer */ - rval = vStream_AudioOut->SetBuf(audio_out_buf, sizeof(audio_out_buf), OUT_BLOCK_SIZE); - if (rval != VSTREAM_OK) { - printf("Failed to set audio output buffer.\n"); - for(;;); - } + rval = vStream_AudioOut->SetBuf(audio_out_buf, sizeof(audio_out_buf), AUDIO_BUF_BLOCK_SIZE); + ASSERT(rval == VSTREAM_OK); - /* Get a pointer to a block of memory for the output samples */ + /* Output stream will fail to start without data to output. */ + /* Prepare first block of data and release it to the driver. */ out_block = vStream_AudioOut->GetBlock(); - if (out_block == NULL) { - printf("Failed to get a pointer to audio output block.\n"); - for(;;); - } + ASSERT(out_block != NULL); - /* Clear output frame */ - memset(out_block, 0x00, OUT_BLOCK_SIZE); + memset(out_block, 0x00, AUDIO_BUF_BLOCK_SIZE); - /* Release a block of memory holding the output samples */ rval = vStream_AudioOut->ReleaseBlock(); - if (rval != VSTREAM_OK) { - printf("Failed to release audio output block.\n"); - } + ASSERT(rval == VSTREAM_OK); +} - /* Start audio output */ - rval = vStream_AudioOut->Start(VSTREAM_MODE_CONTINUOUS); - if (rval != VSTREAM_OK) { - printf("Failed to start audio output stream.\n"); - for(;;); - } +/* + Application main thread. +*/ +__NO_RETURN void app_main_thread (void *argument) { + int32_t rval; + uint32_t count; + uint32_t flags; + void *in_block; + void *out_block; + + /* Initialize input and output streams */ + init_streams(); - /* Start audio capture */ + /* Start input and output streams */ rval = vStream_AudioIn->Start(VSTREAM_MODE_CONTINUOUS); - if (rval != VSTREAM_OK) { - printf("Failed to start audio input stream.\n"); - for(;;); - } + ASSERT(rval == VSTREAM_OK); + + rval = vStream_AudioOut->Start(VSTREAM_MODE_CONTINUOUS); + ASSERT(rval == VSTREAM_OK); - /* Continuous streaming loop */ - while(1) { + printf("Audio streaming started...\n"); + + /* Input to Output copy loop */ + for (count = 0; (AUDIO_TOTAL_BLOCKS == 0) || (count < AUDIO_TOTAL_BLOCKS); count++) { /* Wait for new audio input samples */ flags = osThreadFlagsWait(FRAME_RECEIVED | END_OF_STREAM, osFlagsWaitAny, osWaitForever); @@ -205,7 +137,6 @@ __NO_RETURN void app_main_thread (void *argument) { /* Get a pointer to a block of memory holding the input samples */ in_block = vStream_AudioIn->GetBlock(); if (in_block == NULL) { - printf("Failed to get a pointer to audio input block.\n"); break; } @@ -215,41 +146,31 @@ __NO_RETURN void app_main_thread (void *argument) { /* Get a pointer to a block of memory for the output samples */ out_block = vStream_AudioOut->GetBlock(); if (out_block == NULL) { - printf("Failed to get a pointer to audio output block.\n"); break; } - /* Copy input frame to output frame */ - memcpy(out_block, in_block, OUT_BLOCK_SIZE); + /* Copy audio samples from input to output */ + memcpy(out_block, in_block, AUDIO_BUF_BLOCK_SIZE); /* Release a block of memory holding the input audio */ rval = vStream_AudioIn->ReleaseBlock(); if (rval != VSTREAM_OK) { - printf("Failed to release audio input block.\n"); break; } - /* Release a block of memory holding the output audio */ + /* Release a block of memory for the output audio */ rval = vStream_AudioOut->ReleaseBlock(); if (rval != VSTREAM_OK) { - printf("Failed to release audio output block.\n"); break; } } - /* Stop input stream */ + /* End of stream, stop streaming */ rval = vStream_AudioIn->Stop(); - if (rval != VSTREAM_OK) { - printf("Failed to stop input stream.\n"); - for(;;); - } + ASSERT(rval == VSTREAM_OK); - /* Stop output stream */ rval = vStream_AudioOut->Stop(); - if (rval != VSTREAM_OK) { - printf("Failed to stop output stream.\n"); - for(;;); - } + ASSERT(rval == VSTREAM_OK); printf("Audio streaming stopped.\n"); for(;;); diff --git a/example/FVP_Audio/project/app_main.h b/example/FVP_Audio/project/app_main.h new file mode 100644 index 00000000..3a11f3c3 --- /dev/null +++ b/example/FVP_Audio/project/app_main.h @@ -0,0 +1,54 @@ +/* + * Copyright 2026 Arm Limited and/or its affiliates. + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 + * + * 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. + */ + +#ifndef APP_MAIN_H_ +#define APP_MAIN_H_ + +#include +#include + +#include "vstream_audio_in.h" +#include "vstream_audio_out.h" + +#include "app_config.h" + +/* References to the underlying CMSIS vStream driver */ +#define vStream_AudioIn (&Driver_vStreamAudioIn) +#define vStream_AudioOut (&Driver_vStreamAudioOut) + +/* The size of one data block that holds 100ms of audio samples */ +#define AUDIO_BUF_BLOCK_SIZE (AUDIO_SAMPLE_RATE * (AUDIO_SAMPLE_BITS/8) * AUDIO_CHANNELS / 10) + +/* The number of blocks in audio buffer */ +#define AUDIO_BUF_BLOCK_CNT (2U) + +/* Determine how many blocks to transfer for the required streaming duration */ +#define AUDIO_TOTAL_BLOCKS ((AUDIO_STREAM_DURATION * AUDIO_SAMPLE_RATE) / (AUDIO_BUF_BLOCK_SIZE / (AUDIO_SAMPLE_BITS/8) / AUDIO_CHANNELS)) + +/* Custom assert that uses stdout instead of stderr */ +#ifndef ASSERT +#define ASSERT(expr) do { \ + if (!(expr)) { \ + printf("ASSERT FAILED: %s, %s, line %d\n", #expr, __FILE__, __LINE__); \ + exit(0); \ + } \ + } while (0) +#endif + + +#endif /* APP_MAIN_H_ */ \ No newline at end of file From 255bcd5de6e5531e397d6ed9a3165f1338a0ea27 Mon Sep 17 00:00:00 2001 From: Vladimir Umek Date: Thu, 19 Feb 2026 16:03:52 +0100 Subject: [PATCH 2/3] example: update FVP_Audio --- example/FVP_Audio/FVP_Audio.csolution.yml | 4 ---- example/FVP_Audio/project/app_main.c | 21 +++++++-------------- example/FVP_Audio/project/app_main.h | 7 +++---- 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/example/FVP_Audio/FVP_Audio.csolution.yml b/example/FVP_Audio/FVP_Audio.csolution.yml index 5fa12c36..a8552373 100644 --- a/example/FVP_Audio/FVP_Audio.csolution.yml +++ b/example/FVP_Audio/FVP_Audio.csolution.yml @@ -17,7 +17,6 @@ solution: # List different hardware targets that are used to deploy the solution. target-types: - # Arm Corstone-300 targets - type: SSE-300 board: V2M-MPS3-SSE-300-FVP device: SSE-300-MPS3 @@ -33,7 +32,6 @@ solution: images: - project-context: FVP_Audio.Debug - # Arm Corstone-310 targets - type: SSE-310 board: V2M-MPS3-SSE-310 device: SSE-310-MPS3_FVP @@ -49,7 +47,6 @@ solution: images: - project-context: FVP_Audio.Debug - # Arm Corstone-315 targets - type: SSE-315 board: SSE-315 device: SSE-315-FVP @@ -65,7 +62,6 @@ solution: images: - project-context: FVP_Audio.Debug - # Arm Corstone-320 targets - type: SSE-320 board: SSE-320 device: SSE-320-FVP diff --git a/example/FVP_Audio/project/app_main.c b/example/FVP_Audio/project/app_main.c index 1219067e..91235e5b 100644 --- a/example/FVP_Audio/project/app_main.c +++ b/example/FVP_Audio/project/app_main.c @@ -24,6 +24,7 @@ #include "cmsis_os2.h" #include "app_main.h" +#include "app_config.h" /* Attributes for the app_main thread */ const osThreadAttr_t thread_attr_main = { .name = "app_main" }; @@ -73,7 +74,7 @@ void AudioOut_Event_Callback (uint32_t event) { This function initializes the input and output streams, registers the event callbacks and sets up the stream buffers. */ -void init_streams (void) { +static void init_streams (void) { int32_t rval; void *out_block; @@ -136,33 +137,25 @@ __NO_RETURN void app_main_thread (void *argument) { } /* Get a pointer to a block of memory holding the input samples */ in_block = vStream_AudioIn->GetBlock(); - if (in_block == NULL) { - break; - } + ASSERT(in_block != NULL); /* Wait for available output block */ osThreadFlagsWait(FRAME_SENT, osFlagsWaitAny, osWaitForever); /* Get a pointer to a block of memory for the output samples */ out_block = vStream_AudioOut->GetBlock(); - if (out_block == NULL) { - break; - } + ASSERT(out_block != NULL); /* Copy audio samples from input to output */ memcpy(out_block, in_block, AUDIO_BUF_BLOCK_SIZE); /* Release a block of memory holding the input audio */ rval = vStream_AudioIn->ReleaseBlock(); - if (rval != VSTREAM_OK) { - break; - } + ASSERT(rval == VSTREAM_OK); /* Release a block of memory for the output audio */ rval = vStream_AudioOut->ReleaseBlock(); - if (rval != VSTREAM_OK) { - break; - } + ASSERT(rval == VSTREAM_OK); } /* End of stream, stop streaming */ @@ -172,7 +165,7 @@ __NO_RETURN void app_main_thread (void *argument) { rval = vStream_AudioOut->Stop(); ASSERT(rval == VSTREAM_OK); - printf("Audio streaming stopped.\n"); + printf("Audio streaming stopped.\x04\n"); for(;;); } diff --git a/example/FVP_Audio/project/app_main.h b/example/FVP_Audio/project/app_main.h index 3a11f3c3..6373d68e 100644 --- a/example/FVP_Audio/project/app_main.h +++ b/example/FVP_Audio/project/app_main.h @@ -19,8 +19,7 @@ #ifndef APP_MAIN_H_ #define APP_MAIN_H_ -#include -#include +#include #include "vstream_audio_in.h" #include "vstream_audio_out.h" @@ -45,10 +44,10 @@ #define ASSERT(expr) do { \ if (!(expr)) { \ printf("ASSERT FAILED: %s, %s, line %d\n", #expr, __FILE__, __LINE__); \ - exit(0); \ + for(;;); \ } \ } while (0) #endif -#endif /* APP_MAIN_H_ */ \ No newline at end of file +#endif /* APP_MAIN_H_ */ From b1d424c108d638bd38900207cee54bac27b86706 Mon Sep 17 00:00:00 2001 From: Vladimir Umek Date: Thu, 19 Feb 2026 15:43:45 +0100 Subject: [PATCH 3/3] example: simplify FVP_Video example - Move configuration separately (into app_config.h) - Move auxiliary definitions to accompanying header (app_main.h) - Remove thread_stdin and use define to set streaming duration - Add custom ASSERT to check return values - Move stream initialization to init_streams function - Add handling for different input/output resolution (crop, center copy into output frame) --- example/FVP_Video/README.md | 29 ++- .../FVP_Video/project/FVP_Video.cproject.yml | 4 +- example/FVP_Video/project/app_config.h | 73 ++++++ example/FVP_Video/project/app_main.c | 218 ++++++------------ example/FVP_Video/project/app_main.h | 60 +++++ example/FVP_Video/project/frame_copy.c | 169 ++++++++++++++ 6 files changed, 391 insertions(+), 162 deletions(-) create mode 100644 example/FVP_Video/project/app_config.h create mode 100644 example/FVP_Video/project/app_main.h create mode 100644 example/FVP_Video/project/frame_copy.c diff --git a/example/FVP_Video/README.md b/example/FVP_Video/README.md index 17460a0a..51dd8c36 100644 --- a/example/FVP_Video/README.md +++ b/example/FVP_Video/README.md @@ -8,19 +8,32 @@ It shows how to capture video frames from an input stream and output them throug ## Operation The application initializes both video input and output streams and then continuously -captures video frames from the input stream and copies them directly to the output -stream. +captures video frames from the input stream and copies them to the output stream. +Application assumes that the video frame pixel format is RGB888. + +If input and output stream resolutions differ the input frame is centered within the +output frame and/or cropped when required. + +Streaming stops after receiving the `END_OF_STREAM` event or can be configured to +stop after `AUDIO_STREAM_DURATION` seconds. The example uses CMSIS-RTOS2 for thread management and event-driven processing. ### Configuration -Default configuration expects the following input and output stream format: +Default configuration ([app_config.h](project/app_config.h)) expects the following input +and output stream format: + +- 640 x 480 pixels, 30FPS + +To change the default configuration use the following defines: -- 320 x 240 pixels, 3 byte per pixel +- `VIDEO_IN_FRAME_WIDTH` - the input video stream frame width in pixels +- `VIDEO_IN_FRAME_HEIGHT` - the input video stream frame height in pixels +- `VIDEO_OUT_FRAME_WIDTH` - the output video stream frame width in pixels +- `VIDEO_OUT_FRAME_HEIGHT` - the output video stream frame height in pixels +- `VIDEO_FRAME_RATE` - the video frame rate in frames per second -To change default configuration use configuration defines: +The default streaming duration is indefinite. To change this, use: -- `VIDEO_FRAME_WIDTH` - the video stream frame width in pixels -- `VIDEO_FRAME_HEIGHT` - the video stream frame height in pixels -- `VIDEO_PIXEL_SIZE` - the video stream bytes per pixel +- `VIDEO_STREAM_DURATION` - sets the streaming duration in seconds (0: stream indefinitely) diff --git a/example/FVP_Video/project/FVP_Video.cproject.yml b/example/FVP_Video/project/FVP_Video.cproject.yml index 2733c512..93092494 100644 --- a/example/FVP_Video/project/FVP_Video.cproject.yml +++ b/example/FVP_Video/project/FVP_Video.cproject.yml @@ -28,6 +28,8 @@ project: - group: Application files: - file: app_main.c + - file: app_config.h + - file: frame_copy.c - group: Documentation files: @@ -42,5 +44,5 @@ project: output: type: - elf - - bin + - hex - map diff --git a/example/FVP_Video/project/app_config.h b/example/FVP_Video/project/app_config.h new file mode 100644 index 00000000..a9856e5c --- /dev/null +++ b/example/FVP_Video/project/app_config.h @@ -0,0 +1,73 @@ +/* + * Copyright 2026 Arm Limited and/or its affiliates. + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 + * + * 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. + */ + +#ifndef APP_CONFIG_H_ +#define APP_CONFIG_H_ + +//-------- <<< Use Configuration Wizard in Context Menu >>> -------------------- +//------ With VS Code: Open Preview for Configuration Wizard ------------------- + + +// Stream duration (seconds) +// Defines the streaming duration in seconds. +// Default: 0 (stream indefinitely) +#ifndef VIDEO_STREAM_DURATION +#define VIDEO_STREAM_DURATION 0 +#endif + +// Video Configuration +// ====================== + +// Input Frame Width +// Define the input video frame width. +// Default: 640 +#ifndef VIDEO_IN_FRAME_WIDTH +#define VIDEO_IN_FRAME_WIDTH 640 +#endif + +// Input Frame Height +// Define the input video frame height. +// Default: 480 +#ifndef VIDEO_IN_FRAME_HEIGHT +#define VIDEO_IN_FRAME_HEIGHT 480 +#endif + +// Output Frame Width +// Define the output video frame width. +// Default: 640 +#ifndef VIDEO_OUT_FRAME_WIDTH +#define VIDEO_OUT_FRAME_WIDTH 640 +#endif + +// Output Frame Height +// Define the output video frame height. +// Default: 480 +#ifndef VIDEO_OUT_FRAME_HEIGHT +#define VIDEO_OUT_FRAME_HEIGHT 480 +#endif + +// Frame Rate +// Define the video frame rate in frames per second (fps). +// Default: 30 +#ifndef VIDEO_FRAME_RATE +#define VIDEO_FRAME_RATE 30 +#endif + +// + +#endif /* APP_CONFIG_H_ */ diff --git a/example/FVP_Video/project/app_main.c b/example/FVP_Video/project/app_main.c index e3a22c38..e9cf6369 100644 --- a/example/FVP_Video/project/app_main.c +++ b/example/FVP_Video/project/app_main.c @@ -1,5 +1,5 @@ -/*--------------------------------------------------------------------------- - * Copyright (c) 2025 Arm Limited (or its affiliates). All rights reserved. +/* + * Copyright 2025-2026 Arm Limited and/or its affiliates. * * SPDX-License-Identifier: Apache-2.0 * @@ -14,7 +14,7 @@ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - *---------------------------------------------------------------------------*/ + */ #include #include @@ -23,75 +23,23 @@ #include "cmsis_vstream.h" #include "cmsis_os2.h" -#include "main.h" - - -/* Define the video stream frame width in pixels. */ -#ifndef VIDEO_FRAME_WIDTH -#define VIDEO_FRAME_WIDTH 640 -#endif - -/* Define the video stream frame height in pixels. */ -#ifndef VIDEO_FRAME_HEIGHT -#define VIDEO_FRAME_HEIGHT 480 -#endif - -/* Define the video stream bytes per pixel */ -#ifndef VIDEO_PIXEL_SIZE -#define VIDEO_PIXEL_SIZE 3 -#endif - - -/* Define input and output block size (frame width x frame height x bytes per pixel) */ -#define IN_BLOCK_SIZE (VIDEO_FRAME_WIDTH * VIDEO_FRAME_HEIGHT * VIDEO_PIXEL_SIZE) -#define OUT_BLOCK_SIZE (VIDEO_FRAME_WIDTH * VIDEO_FRAME_HEIGHT * VIDEO_PIXEL_SIZE) - -/* Define number of blocks in input and output buffers */ -#define IN_BLOCK_CNT (2U) -#define OUT_BLOCK_CNT (2U) +#include "app_main.h" +#include "app_config.h" -/* References to the underlying CMSIS vStream driver */ -extern vStreamDriver_t Driver_vStreamVideoIn; -#define vStream_VideoIn (&Driver_vStreamVideoIn) - -extern vStreamDriver_t Driver_vStreamVideoOut; -#define vStream_VideoOut (&Driver_vStreamVideoOut) - - -/* Input and output stream buffers */ -uint8_t video_in_buf[IN_BLOCK_SIZE * IN_BLOCK_CNT] __attribute__((aligned(32))); -uint8_t video_out_buf[OUT_BLOCK_SIZE * OUT_BLOCK_CNT] __attribute__((aligned(32))); - -/* Thread attributes */ +/* Attributes for the app_main thread */ const osThreadAttr_t thread_attr_main = { .name = "app_main" }; -const osThreadAttr_t thread_attr_stdin = { .name = "stdin" }; -/* Thread IDs */ +/* ID of the app_main thread */ osThreadId_t thread_id_main; -osThreadId_t thread_id_stdin; - -/* Define stream related thread flags */ -#define FRAME_RECEIVED 1 -#define FRAME_SENT 2 -#define END_OF_STREAM 4 - -/* - Thread that waits for stdin characters. -*/ -__NO_RETURN void thread_stdin (void *argument) { - int ch; - printf("Press any key to stop streaming...\n"); +/* Input and output stream buffers */ +uint8_t video_in_buf[VIDEO_IN_BUF_BLOCK_SIZE * VIDEO_IN_BUF_BLOCK_CNT] __attribute__((aligned(32))); +uint8_t video_out_buf[VIDEO_OUT_BUF_BLOCK_SIZE * VIDEO_OUT_BUF_BLOCK_CNT] __attribute__((aligned(32))); - while (1) { - ch = getchar(); - if (ch != EOF) { - /* Signal end of stream */ - osThreadFlagsSet(thread_id_main, END_OF_STREAM); - } - osDelay(100); - } -} +/* Define stream related thread flags */ +#define FRAME_RECEIVED (1U << 0) +#define FRAME_SENT (1U << 1) +#define END_OF_STREAM (1U << 2) /* Input Stream Event Callback @@ -121,80 +69,64 @@ void VideoOut_Event_Callback (uint32_t event) { } /* - Application main thread. + Initialize streams. + + This function initializes the input and output streams, registers the + event callbacks and sets up the stream buffers. */ -__NO_RETURN void app_main_thread (void *argument) { - int32_t rval; - uint32_t flags; - void *in_block; +static void init_streams (void) { + int32_t rval; void *out_block; - /* Create stdin thread */ - thread_id_stdin = osThreadNew(thread_stdin, NULL, &thread_attr_stdin); - if (thread_id_stdin == NULL) { - printf("Failed to create stdin thread.\n"); - } - - /* Initialize Video Input Stream */ + /* Setup input stream */ rval = vStream_VideoIn->Initialize(VideoIn_Event_Callback); - if (rval != VSTREAM_OK) { - printf("Failed to initialize video input stream.\n"); - for(;;); - } + ASSERT(rval == VSTREAM_OK); - /* Set Input Video buffer */ - rval = vStream_VideoIn->SetBuf(video_in_buf, sizeof(video_in_buf), IN_BLOCK_SIZE); - if (rval != VSTREAM_OK) { - printf("Failed to set video input buffer.\n"); - for(;;); - } + rval = vStream_VideoIn->SetBuf(video_in_buf, sizeof(video_in_buf), VIDEO_IN_BUF_BLOCK_SIZE); + ASSERT(rval == VSTREAM_OK); - /* Initialize Video Output Stream */ + /* Setup output stream */ rval = vStream_VideoOut->Initialize(VideoOut_Event_Callback); - if (rval != VSTREAM_OK) { - printf("Failed to initialize video output stream.\n"); - for(;;); - } + ASSERT(rval == VSTREAM_OK); - /* Set Output Video buffer */ - rval = vStream_VideoOut->SetBuf(video_out_buf, sizeof(video_out_buf), OUT_BLOCK_SIZE); - if (rval != VSTREAM_OK) { - printf("Failed to set video output buffer.\n"); - for(;;); - } + rval = vStream_VideoOut->SetBuf(video_out_buf, sizeof(video_out_buf), VIDEO_OUT_BUF_BLOCK_SIZE); + ASSERT(rval == VSTREAM_OK); - /* Get a pointer to a block of memory for the output frame */ + /* Output stream will fail to start without data to output. */ + /* Prepare first block of data and release it to the driver. */ out_block = vStream_VideoOut->GetBlock(); - if (out_block == NULL) { - printf("Failed to get a pointer to video output block.\n"); - for(;;); - } + ASSERT(out_block != NULL); - /* Clear output frame */ - memset(out_block, 0x00, OUT_BLOCK_SIZE); + memset(out_block, 0x00, VIDEO_OUT_BUF_BLOCK_SIZE); - /* Release a block of memory holding the output frame */ rval = vStream_VideoOut->ReleaseBlock(); - if (rval != VSTREAM_OK) { - printf("Failed to release video output block.\n"); - } + ASSERT(rval == VSTREAM_OK); +} - /* Start video output */ - rval = vStream_VideoOut->Start(VSTREAM_MODE_CONTINUOUS); - if (rval != VSTREAM_OK) { - printf("Failed to start video output stream.\n"); - for(;;); - } +/* + Application main thread. +*/ +__NO_RETURN void app_main_thread (void *argument) { + int32_t rval; + uint32_t count; + uint32_t flags; + void *in_block; + void *out_block; - /* Start video capture */ + /* Initialize input and output streams */ + init_streams(); + + /* Start input and output streams */ rval = vStream_VideoIn->Start(VSTREAM_MODE_CONTINUOUS); - if (rval != VSTREAM_OK) { - printf("Failed to start video input stream.\n"); - for(;;); - } + ASSERT(rval == VSTREAM_OK); + + rval = vStream_VideoOut->Start(VSTREAM_MODE_CONTINUOUS); + ASSERT(rval == VSTREAM_OK); - /* Continuous streaming loop */ - while(1) { + printf("Video streaming started...\n"); + + /* Input to Output copy loop */ + for (count = 0; (VIDEO_TOTAL_BLOCKS == 0) || (count < VIDEO_TOTAL_BLOCKS); count++) { /* Wait for new video input frame */ flags = osThreadFlagsWait(FRAME_RECEIVED | END_OF_STREAM, osFlagsWaitAny, osWaitForever); @@ -206,55 +138,35 @@ __NO_RETURN void app_main_thread (void *argument) { /* Get a pointer to a block of memory holding the input frame */ in_block = vStream_VideoIn->GetBlock(); - if (in_block == NULL) { - printf("Failed to get a pointer to video input block.\n"); - break; - } + ASSERT(in_block != NULL); /* Wait for available output frame */ osThreadFlagsWait(FRAME_SENT, osFlagsWaitAny, osWaitForever); /* Get a pointer to a block of memory for the output frame */ out_block = vStream_VideoOut->GetBlock(); - if (out_block == NULL) { - printf("Failed to get a pointer to video output block.\n"); - break; - } - - /* Copy input frame to output frame */ - memcpy(out_block, in_block, OUT_BLOCK_SIZE); + ASSERT(out_block != NULL); + /* Copy input frame to output frame with cropping */ + frame_copy(in_block, out_block); /* Release a block of memory holding the input frame */ rval = vStream_VideoIn->ReleaseBlock(); - if (rval != VSTREAM_OK) { - printf("Failed to release video input block.\n"); - break; - } + ASSERT(rval == VSTREAM_OK); /* Release a block of memory holding the output frame */ rval = vStream_VideoOut->ReleaseBlock(); - if (rval != VSTREAM_OK) { - printf("Failed to release video output block.\n"); - break; - } + ASSERT(rval == VSTREAM_OK); } - /* Stop input stream */ + /* End of stream, stop streaming */ rval = vStream_VideoIn->Stop(); - if (rval != VSTREAM_OK) { - printf("Failed to stop input stream.\n"); - for(;;); - } + ASSERT(rval == VSTREAM_OK); - /* Stop output stream */ rval = vStream_VideoOut->Stop(); - if (rval != VSTREAM_OK) { - printf("Failed to stop output stream.\n"); - for(;;); - } + ASSERT(rval == VSTREAM_OK); - printf("Video streaming stopped.\n"); + printf("Video streaming stopped.\x04\n"); for(;;); } diff --git a/example/FVP_Video/project/app_main.h b/example/FVP_Video/project/app_main.h new file mode 100644 index 00000000..9943d536 --- /dev/null +++ b/example/FVP_Video/project/app_main.h @@ -0,0 +1,60 @@ +/* + * Copyright 2026 Arm Limited and/or its affiliates. + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 + * + * 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. + */ + +#ifndef APP_MAIN_H_ +#define APP_MAIN_H_ + +#include + +#include "vstream_video_in.h" +#include "vstream_video_out.h" + +#include "app_config.h" + +/* References to the underlying CMSIS vStream driver */ +#define vStream_VideoIn (&Driver_vStreamVideoIn) +#define vStream_VideoOut (&Driver_vStreamVideoOut) + +/* Number of bytes per pixel for RGB888 format */ +#define VIDEO_PIXEL_SIZE (3U) + +/* The size of data blocks that hold one video frame */ +#define VIDEO_IN_BUF_BLOCK_SIZE (VIDEO_IN_FRAME_WIDTH * VIDEO_IN_FRAME_HEIGHT * VIDEO_PIXEL_SIZE) +#define VIDEO_OUT_BUF_BLOCK_SIZE (VIDEO_OUT_FRAME_WIDTH * VIDEO_OUT_FRAME_HEIGHT * VIDEO_PIXEL_SIZE) + +/* The number of blocks in video buffer */ +#define VIDEO_IN_BUF_BLOCK_CNT (2U) +#define VIDEO_OUT_BUF_BLOCK_CNT (2U) + +/* Determine how many blocks to transfer for the required streaming duration */ +#define VIDEO_TOTAL_BLOCKS (VIDEO_STREAM_DURATION * VIDEO_FRAME_RATE) + +/* Custom assert that uses stdout instead of stderr */ +#ifndef ASSERT +#define ASSERT(expr) do { \ + if (!(expr)) { \ + printf("ASSERT FAILED: %s, %s, line %d\n", #expr, __FILE__, __LINE__); \ + for(;;); \ + } \ + } while (0) +#endif + +/* Function to copy a video frame from input to output */ +extern int32_t frame_copy(const void *in_frame, void *out_frame); + +#endif /* APP_MAIN_H_ */ diff --git a/example/FVP_Video/project/frame_copy.c b/example/FVP_Video/project/frame_copy.c new file mode 100644 index 00000000..8de4fb4f --- /dev/null +++ b/example/FVP_Video/project/frame_copy.c @@ -0,0 +1,169 @@ +/* + * Copyright 2025-2026 Arm Limited and/or its affiliates. + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 + * + * 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. + */ + +#include +#include + +#include "app_main.h" +#include "app_config.h" + +/** + Copy a smaller or equally sized image into a destination buffer at a given offset. + + Assumes that the source image fits completely within the destination at the specified offset. + It assumes both source and destination use the RGB888 format. + + \param src Pointer to the source image buffer. + \param src_width Width of the source image in pixels. + \param src_height Height of the source image in pixels. + \param dst Pointer to the destination framebuffer buffer. + \param dst_width Width of the destination framebuffer in pixels. + \param dst_height Height of the destination framebuffer in pixels. + \param x_offset X offset in the framebuffer where the image should be placed. + \param y_offset Y offset in the framebuffer where the image should be placed. + \param format Pixel format for both source and destination (must match). +*/ +static void copy_RGB888(const uint8_t *src, + int src_width, + int src_height, + uint8_t *dst, + int dst_width, + int dst_height, + int x_offset, + int y_offset) { + int bpp = 3; + + for (int y = 0; y < src_height; ++y) { + int dst_y = y + y_offset; + if (dst_y < 0 || dst_y >= dst_height) + continue; + + for (int x = 0; x < src_width; ++x) { + int dst_x = x + x_offset; + if (dst_x < 0 || dst_x >= dst_width) + continue; + + int src_idx = (y * src_width + x) * bpp; + int dst_idx = (dst_y * dst_width + dst_x) * bpp; + + for (int i = 0; i < bpp; ++i) { + dst[dst_idx + i] = src[src_idx + i]; + } + } + } +} + +/** + \brief Crop a region from an RGB888 image. + + The function copies a cropped rectangle from a source RGB888 image and writes + it to a destination buffer in RGB888 format. + + \param src Pointer to the input RGB888 image buffer. + \param src_width Width of the source image in pixels. + \param src_height Height of the source image in pixels. + \param dst Pointer to the output RGB888 buffer. + \param crop_x X coordinate of the top-left corner of the crop region. + \param crop_y Y coordinate of the top-left corner of the crop region. + \param crop_width Width of the crop region in pixels. + \param crop_height Height of the crop region in pixels. +*/ +static void crop_rgb888(const uint8_t *src, + int src_width, + int src_height, + uint8_t *dst, + int crop_x, + int crop_y, + int crop_width, + int crop_height) +{ + const int bpp = 3; + + for (int y = 0; y < crop_height; ++y) { + int src_y = crop_y + y; + if (src_y >= src_height) break; // Prevent out-of-bounds + + const uint8_t *src_row = src + (src_y * src_width + crop_x) * bpp; + uint8_t *dst_row = dst + (y * crop_width) * bpp; + + for (int x = 0; x < crop_width; ++x) { + int src_x = x; + if ((crop_x + src_x) >= src_width) break; + + const uint8_t *src_pixel = src_row + src_x * bpp; + uint8_t *dst_pixel = dst_row + x * bpp; + + dst_pixel[0] = src_pixel[0]; // R + dst_pixel[1] = src_pixel[1]; // G + dst_pixel[2] = src_pixel[2]; // B + } + } +} + +/** + Copy a video frame from input to output with automatic centering and/or cropping. + + This function adapts and copies input video frame to the output frame. The behavior + depends on the relative sizes of input and output frames: + - If dimensions are equal: Direct memory copy + - If input is smaller (in one or both dimensions): Centers the input within the output + - If input is larger (in one or both dimensions): Crops the center region of the input + + Centering and cropping are always applied from the center to maintain visual balance. + The output buffer is filled with zeros (black) for any areas not covered by the input. + + The function assumes that both input and output frames are in RGB888 format. + + \param in_frame Pointer to the input frame buffer. + \param out_frame Pointer to the output frame buffer. + \return int32_t 0 on success, -1 if configuration is unsupported. +*/ +int32_t frame_copy(const void *in_frame, void *out_frame) { +#if (VIDEO_IN_FRAME_WIDTH == VIDEO_OUT_FRAME_WIDTH) && (VIDEO_IN_FRAME_HEIGHT == VIDEO_OUT_FRAME_HEIGHT) + /* Input and output dimensions are the same, perform a direct copy */ + memcpy(out_frame, in_frame, VIDEO_OUT_BUF_BLOCK_SIZE); + return 0; +#elif ((VIDEO_IN_FRAME_WIDTH < VIDEO_OUT_FRAME_WIDTH) && (VIDEO_IN_FRAME_HEIGHT <= VIDEO_OUT_FRAME_HEIGHT)) || \ + ((VIDEO_IN_FRAME_WIDTH <= VIDEO_OUT_FRAME_WIDTH) && (VIDEO_IN_FRAME_HEIGHT < VIDEO_OUT_FRAME_HEIGHT)) + /* Input frame is smaller than output frame, copy input to the output center */ + copy_RGB888((const uint8_t *)in_frame, + VIDEO_IN_FRAME_WIDTH, + VIDEO_IN_FRAME_HEIGHT, + (uint8_t *)out_frame, + VIDEO_OUT_FRAME_WIDTH, + VIDEO_OUT_FRAME_HEIGHT, + (VIDEO_OUT_FRAME_WIDTH - VIDEO_IN_FRAME_WIDTH) / 2, + (VIDEO_OUT_FRAME_HEIGHT - VIDEO_IN_FRAME_HEIGHT) / 2); + return 0; +#elif ((VIDEO_IN_FRAME_WIDTH > VIDEO_OUT_FRAME_WIDTH) && (VIDEO_IN_FRAME_HEIGHT >= VIDEO_OUT_FRAME_HEIGHT)) || \ + ((VIDEO_IN_FRAME_WIDTH >= VIDEO_OUT_FRAME_WIDTH) && (VIDEO_IN_FRAME_HEIGHT > VIDEO_OUT_FRAME_HEIGHT)) + /* Input frame is larger than output frame, crop center of input image and copy to output */ + crop_rgb888((const uint8_t *)in_frame, + VIDEO_IN_FRAME_WIDTH, + VIDEO_IN_FRAME_HEIGHT, + (uint8_t *)out_frame, + (VIDEO_IN_FRAME_WIDTH - VIDEO_OUT_FRAME_WIDTH) / 2, + (VIDEO_IN_FRAME_HEIGHT - VIDEO_OUT_FRAME_HEIGHT) / 2, + VIDEO_OUT_FRAME_WIDTH, + VIDEO_OUT_FRAME_HEIGHT); + return 0; +#else + /* Unsupported configuration */ + return -1; +#endif +}