Skip to main content

Recorder

|

The Recorder module is a comprehensive tool designed for managing sensor data recording.

The Recorder supports a wide range of configurable parameters through the RecorderConfiguration, allowing developers to tailor recording behavior to specific application needs. Features include:

  • Customizable storage options: Define log directories, manage disk space, and specify the duration of recordings.
  • Data type selection: Specify which data types (e.g., video, audio, or sensor data) to record.
  • Video and audio options: Set video resolution, enable/disable audio recording, and manage chunk durations. It's possible to record only sensor data, sensor data + video, sensor data + audio or sensor data + video + audio.

The Recorder provides comprehensive methods to control the recording lifecycle:

  • Start, stop, pause, and resume recordings.
  • Automatic restarts for continuous recording with chunked durations.

The Recorder supports various transportation modes (e.g., car, pedestrian, bike), enabling detailed analysis and classification of recordings based on context.

To prevent overwhelming the device's storage, the Recorder allows for setting disk space limits and automatically manages logs based on specified retention thresholds.

In the following class diagram you can see the main classes used by the Recorder and the relationships between them:

Recorder

Initialize the Recorder

The Recorder cannot be directly instantiated. Instead, the produce method is used to obtain a configured Recorder instance.

RecorderConfigurationPtr recorderConfiguration = gem::StrongPointerFactory<RecorderConfiguration>();

recorderConfiguration->dataSource = yourDataSource;
recorderConfiguration->logsDir = pathToRecordDirectory;
recorderConfiguration->recordedTypes.push_back( sense::EDataType::Position );

StrongPointer<Recorder> recorder = Recorder::produce( recorderConfiguration );
info

If the dataSource, logsDir and recordedTypes parameters are not populated with valid data, the startRecording method will return error::KInvalidInput and no data will be recorded.

Configure the RecorderConfiguration

Recorder configuration attributeDescription
dataSourceThe source providing the data to be recorded.
logsDirThe directory used to keep the logs
deviceModelThe device model. Deprecated, use hardwareSpecifications instead.
hardwareSpecificationsExtensive details about the device as a hashmap.
recordedTypesThe data types that are recorded
minDurationSecondsThe minimum duration for the recording to be saved
videoQualityThe video quality
chunkDurationSecondsThe chunk duration time in seconds
bContinuousRecordingWhether the recording should continue automatically when chunk time achieved
bEnableAudioThis flag will be used to determine if audio is needed to be recorded or not
maxDiskSpaceUsedWhen reached, it will stop the recording
keepMinSecondsWill not delete any record if this threshold is not reached
deleteOlderThanKeepMinOlder logs that exceeds minimum kept seconds threshold should be deleted
transportModeThe transport mode
warning

If the log duration is shorter than minDurationSeconds the stopRecording method of Recorder will not save the recording and will return error::KRecordedLogTooShort.

The error::KRecordedLogTooShort error may also occur if an insufficient number of positions were emitted, even when the duration between startRecording and stopRecording exceeds minDurationSeconds. To test the recording functionality, you can create a custom external DataSource and push custom positions. Refer to the custom positioning guide for more details. The external DataSource created needs to be provided to the RecorderConfiguration object.

Be aware that if minChunkDuration is set too high, it may cause error::KNoDiskSpace since the SDK will estimate how much space is required for the entire chunk.

The videoQuality parameter is based on the Resolution enum defined below. Each value represents a standard video resolution that affects recording quality and storage requirements.

Camera Resolutions

Resolution Enum ValueDescriptionDimensions (pixels)
UNKNOWNNo resolution set.-
SD_480pStandard Definition640 × 480
HD_720pHigh Definition1280 × 720
FullHD_1080pFull HD1920 × 1080
WQHD_1440pWide Quad HD2560 × 1440
UHD_4K_2160pUltra HD (4K)3840 × 2160
UHD_8K_4320pUltra HD (8K)7680 × 4320
Estimated storage usage

The actual disk usage depends on platform and encoding settings, but here are rough size estimates used internally by the SDK for calculating space requirements:

ResolutionApprox. Bytes/secApprox. MB/min
SD_480p210,000~12 MB/min
HD_720p (iOS)1,048,576~60 MB/min
HD_720p (Android)629,760~37 MB/min
FullHD_1080p3,774,874~130 MB/min

Note: 1 MB = 1,048,576 bytes (binary MB). These are estimates and may vary slightly by platform and encoding settings.

Note: These values are used to pre-check disk availability when chunkDurationSeconds is set.

Recording lifecycle

  1. Starting the Recorder:

    • Call the startRecording method to initiate recording. The recorder transitions to the Recording state.
  2. Pausing and Resuming:

    • Use pauseRecording and resumeRecording to manage interruptions.
  3. Chunked Recordings:

    • If a chunk duration is set in the configuration, the recording automatically stops when the duration is reached. A new recording will begin seamlessly if continuous recording is enabled, ensuring uninterrupted data capture.
  4. Stopping the Recorder:

    • The stopRecording method halts recording, and the system ensures logs meet the configured minimum duration before saving them as .gm files inside logsDir.

How to use the Recorder

The following sections will present some use cases for the recorder, demonstrating its functionality in different scenarios.

auto liveDataSource = sense::DataSourceFactory::produceLive();

String tracksPath = "path/to/record/directory";

if (!liveDataSource){
GEM_INFO_LOG("The datasource could not be created");
return;
}

RecorderConfigurationPtr recorderConfiguration = gem::StrongPointerFactory<RecorderConfiguration>();

recorderConfiguration->dataSource = liveDataSource;
recorderConfiguration->logsDir = tracksPath;
recorderConfiguration->recordedTypes.push_back( sense::EDataType::Position );

// Create recorder based on configuration
StrongPointer<Recorder> recorder = Recorder::produce( recorderConfiguration );

if(!recorder)
{
GEM_INFO_LOG("The recorder could not be created");
return;
}

// Add a listener to monitor recording progress and status updates
recorder->addListener(yourRecorderProgressListenerImpl);

auto errorStart = recorder->startRecording();
if (errorStart != KNoError) {
GEM_INFO_LOG("Error starting recording: %d", errorStart);
}

// Other code

auto errorStop = recorder->stopRecording();
if (errorStop != KNoError) {
GEM_INFO_LOG("Error stopping recording: %d", errorStop);
}
warning

The Recorder will only save data explicitly defined in the recordedTypes list, any other data will be ignored.

warning

The startRecording and stopRecording methods may still do work in background. Design your logic around the status updates delivered on the supplied progress listener.

Record Metrics

The RecordMetrics object provides performance metrics for a recorded activity.

Available only when the recorder is in RecorderStatus.recording status.

Users can access:

  • distanceMeters → The total distance traveled in meters.
  • elevationGainMeters → The total elevation gain in meters.
  • avgSpeedMps → The average speed in meters per second.

These values allow you to analyze ride or workout performance, monitor progress, and build custom dashboards or statistics.

auto errorStart = recorder->startRecording();
if (errorStart != KNoError)
{
GEM_INFO_LOG("Error starting recording: %d", errorStart);
}

// Get the recorder metrics at any time while recording
auto metrics = recorder->getMetrics();

GEM_INFO_LOG("Average speed: %f", metrics.avgSpeedMps);
GEM_INFO_LOG("Distance in meters: %f", metrics.distanceMeters);
GEM_INFO_LOG("Elevation gain: %f", metrics.elevationGainMeters);
tip

The metrics reset at the start of each recording, and once the recording is stopped, the collected data is available in LogMetadata.

Record audio

The recorder also supports audio recording. To enable this functionality, set the bEnableAudio parameter in the RecorderConfiguration to true and invoke the startAudioRecording method from the Recorder class. To stop the audio recording, use the stopAudioRecording method. The following code snippet illustrates this process:

auto liveDataSource = sense::DataSourceFactory::produceLive();

String tracksPath = "path/to/record/directory";

if (!liveDataSource){
GEM_INFO_LOG("The datasource could not be created");
return;
}

RecorderConfigurationPtr recorderConfiguration = gem::StrongPointerFactory<RecorderConfiguration>();

recorderConfiguration->dataSource = liveDataSource;
recorderConfiguration->logsDir = tracksPath;
recorderConfiguration->recordedTypes.push_back( sense::EDataType::Position );
recorderConfiguration->bEnableAudio = true;

// Create recorder based on configuration
StrongPointer<Recorder> recorder = Recorder::produce( recorderConfiguration );

if(!recorder)
{
GEM_INFO_LOG("The recorder could not be created");
return;
}

auto errorStart = recorder->startRecording();
if (errorStart != KNoError) {
GEM_INFO_LOG("Error starting recording: %d", errorStart);
}

// At any moment enable audio recording
recorder->startAudioRecording();

// Other code

// At any moment stop audio recording
recorder->stopAudioRecording();

auto errorStop = recorder->stopRecording();
if (errorStop != KNoError) {
GEM_INFO_LOG("Error stopping recording: %d", errorStop);
}
tip

The audio recording will result in a log file of type .mp4. This file also contains the binary data of a .gm file, but it is accessible by system players.

Record video

The recorder also supports video recording. To enable this functionality, add sense::EDataType::Camera to the recordedTypes and set the videoQuality parameter in the RecorderConfiguration to your desired resolution, we recommend using sense::EResolution::Enum::HD_720p. The video recording starts at the call of the startRecording method and stops at the stopRecording method. The following code snippet illustrates this process:

RecorderConfigurationPtr recorderConfiguration = gem::StrongPointerFactory<RecorderConfiguration>();

recorderConfiguration->dataSource = liveDataSource;
recorderConfiguration->logsDir = tracksPath;
recorderConfiguration->recordedTypes.push_back( sense::EDataType::Position );

recorderConfiguration->recordedTypes.push_back( sense::EDataType::Camera );
recorderConfiguration->videoQuality = sense::EResolution::Enum::HD_720p;

// Create recorder based on configuration
StrongPointer<Recorder> recorder = Recorder::produce( recorderConfiguration );

if(!recorder)
{
GEM_INFO_LOG("The recorder could not be created");
return;
}

auto errorStart = recorder->startRecording();
if (errorStart != KNoError) {
GEM_INFO_LOG("Error starting recording: %d", errorStart);
}

// Other code

auto errorStop = recorder->stopRecording();
if (errorStop != KNoError) {
GEM_INFO_LOG("Error stopping recording: %d", errorStop);
}
tip

The camera recording will result in a log file of type .mp4. This file also contains the binary data of a .gm file, but it is accessible by system players.

warning

Don't forget to request permission for camera usage if you add the sense::EDataType::Camera parameter to recordedTypes.

If using chunkDurationSeconds

When chunkDurationSeconds is set, the SDK will check available disk space before starting the recording.

If there isn't enough space to store an entire chunk (based on the selected resolution), the recorder will not start and will return error::KNoDiskSpace.

Make sure to estimate required storage ahead of time. See Camera Resolutions for expected sizes.

Record multimedia

To record a combination of audio, video and sensors you can do that by setting up the RecorderConfiguration with all the desired functionalities. The following code snippet illustrates this process:

RecorderConfigurationPtr recorderConfiguration = gem::StrongPointerFactory<RecorderConfiguration>();

recorderConfiguration->dataSource = liveDataSource;
recorderConfiguration->logsDir = tracksPath;
recorderConfiguration->recordedTypes.push_back( sense::EDataType::Position );
recorderConfiguration->recordedTypes.push_back( sense::EDataType::Camera );
recorderConfiguration->videoQuality = sense::EResolution::Enum::HD_720p;
recorderConfiguration->bEnableAudio = true;

// Create recorder based on configuration
StrongPointer<Recorder> recorder = Recorder::produce( recorderConfiguration );

if(!recorder)
{
GEM_INFO_LOG("The recorder could not be created");
return;
}

// optional : add a listener to monitor recording progress and status updates
recorder->addListener(yourRecorderProgressListenerImpl);

auto errorStart = recorder->startRecording();
if (errorStart != KNoError) {
GEM_INFO_LOG("Error starting recording: %d", errorStart);
}

// At any moment enable audio recording
recorder->startAudioRecording();

// Other code

// At any moment stop audio recording
recorder->stopAudioRecording();

auto errorStop = recorder->stopRecording();
if (errorStop != KNoError) {
GEM_INFO_LOG("Error stopping recording: %d", errorStop);
}
tip

The audio recording will result in a log file of type .mp4. This file also contains the binary data of a .gm file, but it is accessible by system players.

Recorder Bookmarks and Metadata

The SDK utilizes the proprietary .gm file format for recordings, offering several advantages over standard file types:

  • Supports multiple data types, including acceleration, rotation, and more.
  • Allows embedding custom user data in binary format.
  • Enables automatic storage management by the SDK to optimize space usage.

Recordings are saved as .gm or .mp4 files by the Recorder. The RecorderBookmarks class provides functionality for managing recordings, including exporting .gm or .mp4 files to other formats such as .gpx, as well as importing external formats and converting them to .gm for seamless SDK integration.

Additionally, the LogMetadata class serves as an object-oriented representation of a .gm or .mp4 file, offering features such as retrieving start and end timestamps, coordinates, and path details at varying levels of precision.

The RecorderBookmarks class for enhanced log management:

  • Export and Import Logs: Convert logs to/from different formats such as GPX, NMEA, and KML.
  • Log Metadata: Retrieve details like start and end timestamps, transport mode and size.

In the following class diagram you can see the main classes used by the RecorderBookmarks and the relationships between them:

RecorderBookmarks

Export logs

// Create recorderBookmarks
// It loads all .gm and .mp4 files at logsDir
StrongPointer<RecorderBookmarks> bookmarks = RecorderBookmarks::produce( tracksPath );

if(!bookmarks) return;

// Get list of logs
List<String> logList = bookmarks->getLogsList();

// Export last recording as a GPX file with a given name
// Assumes the logList is not empty
auto exportLogError = bookmarks->exportLog(
logList.back(),
EFileType::Gpx,
"My_File_Name"
);
if (exportLogError != KNoError) {
GEM_INFO_LOG("Error exporting log: %d", exportLogError);
}

The resulting file will be My_File_Name.gpx. If the name of the exported file is not specified, then the log name will be used.

warning

Exporting a .gm file to other formats may result in data loss, depending on the data types supported by each format.

tip

The exported file will be in the same directory as the original log file.

Import logs

Importing logs involves loading a standard file format (such as gpx, nmea or kml) into a .gm file, enabling it to be processed further.

auto importError = bookmarks->importLog("path/to/file", "My_File_Name");
if (importError != KNoError)
{
GEM_INFO_LOG("Error importing log: %d", importError);
}

Access metadata

Each log contains metadata accessible through the LogMetadata class.

StrongPointer<RecorderBookmarks> bookmarks = RecorderBookmarks::produce( logsDir );

if(bookmarks)
{
// Get list of logs
List<String> logList = bookmarks->getLogsList();

StrongPointer<LogMetadata> logMetadata = bookmarks->getMetadata(logList.back());
}
warning

The getMetadata method will return empty strong pointer if the log file does not exist inside the logsDir directory or if the log file is not a valid .gm file.

The metadata within a LogMetadata object contains:

  • getStartPosition / getEndPosition: Geographic coordinates for the log's beginning and end.
  • getUserMetadata / addUserMetadata: Store and retrieve additional data using a key-value approach.
  • getPreciseRoute: A comprehensive list of all recorded coordinates, capturing the highest level of detail possible.
  • getRoute: A list of route coordinates spaced at least 20 meters apart, with a three-second recording delay between each coordinate.
  • getTransportMode: ERecordingTransportMode of recording.
  • getStartTimestampInMillis / getEndTimestampInMillis: Timestamp of the first/last sensor data.
  • getDurationMillis: Log duration.
  • isProtected: Check if a log file is protected. Protected logs will not be automatically deleted after keepMinSeconds specified in RecorderConfiguration.
  • getLogSize: Get the log size in bytes.
  • isDataTypeAvailable: Verify if a data type is produced by the log file.
  • getSoundMarks: A list of recorded soundmarks.
  • getActivityRecord: The recorded activity details.
  • getMetrics: Basic metrics about the recorded log.

To visualize the recorded route, a Path object can be constructed using the route coordinates from the LogMetadata. This path can then be displayed on a map. For more details, refer to the documentation on the path entity and display paths.

Custom user metadata

Users can add custom metadata to a log either during recording or after it has been completed. This is done using the addUserMetadata method, available in both the Recorder and LogMetadata classes. The method requires a String key and the associated data as a DataBuffer.

To retrieve previously added metadata, use the getUserMetadata method of the LogMetadata class.

StrongPointer<LogMetadata> logMetadata = bookmarks->getMetadata(logPath);

DataBuffer imageSample; // fill imageSample with image data

// Save image encoded in DataBuffer
logMetadata->addUserMetadata("ImgData", imageSample);

// Get image
StrongPointer<DataBuffer> imageData = logMetadata->getUserMetadata("ImgData");

// Do something with imageData...

ActivityRecord

The ActivityRecord class captures details about a recorded activity, including descriptions, sport type, effort level, and visibility settings. It ensures comprehensive metadata for recorded sessions.

Attributes

AttributeDescription
shortDescriptionA brief summary of the activity.
longDescriptionA detailed explanation of the activity.
sportTypeThe type of sport involved in the activity.
effortTypeThe intensity of effort (e.g., easy, moderate, hard).
bikeProfileBike profile details (if applicable).
visibilityDefines who can view the activity.

RecordMetrics

The RecordMetrics object provides essential statistics about a recorded log. These metrics are useful for analyzing movement, elevation, and speed data.

AttributeDescription
distanceMetersTotal distance covered during the log, measured in meters.
elevationGainMetersTotal elevation gained over the course of the log, measured in meters.
avgSpeedMpsAverage speed throughout the log, measured in meters per second.

Setting the activity record

auto errorStart = recorder->startRecording();
if (errorStart != KNoError) {
GEM_INFO_LOG("Error starting recording: %d", errorStart);
}

// Other code
ActivityRecord activityRecord;

activityRecord.shortDescription = "Morning Run";
activityRecord.longDescription = "A 5km run through the park.";
activityRecord.sportType = ESportType::Run;
activityRecord.effortType = EffortType::Moderate;
activityRecord.visibility = EVisibility::Everyone;

recorder->setActivityRecord( activityRecord );

// Other code

auto errorStop = recorder->stopRecording();
if (errorStop != KNoError) {
GEM_INFO_LOG("Error stopping recording: %d", errorStop);
}
warning

Call this method while recording, calling it after stopping will not affect the existing recordings.

Getting the activity record

StrongPointer<RecorderBookmarks> bookmarks = RecorderBookmarks::produce( tracksPath );
if (!bookmarks){
GEM_INFO_LOG("Bookmarks could not be created");
return;
}

auto logList = bookmarks->getLogsList();

StrongPointer<LogMetadata> metadata = bookmarks->getMetadata(logList.back());

if (!metadata) {
GEM_INFO_LOG("Log metadata could not be retrieved");
return;
}

ActivityRecord activityRecord = metadata->getActivityRecord();