Commit 86dd9391 authored by Mikhail Karpenko's avatar Mikhail Karpenko

WIP: Write aligned frames to block device

parent d9fcca99
......@@ -11,7 +11,7 @@ IMAGES = $(GUIDIR)/images/filebrowser-01.gif $(GUIDIR)/images/filebrowser-bo
$(GUIDIR)/images/rec_folder.png $(GUIDIR)/images/up_folder.gif $(GUIDIR)/images/play_audio.png $(GUIDIR)/images/hdd.png
SRCS = camogm.c camogm_ogm.c camogm_jpeg.c camogm_mov.c camogm_kml.c camogm_read.c index_list.c
SRCS = camogm.c camogm_ogm.c camogm_jpeg.c camogm_mov.c camogm_kml.c camogm_read.c index_list.c camogm_align.c
TEST_SRC = camogm_test.c
OBJS = $(SRCS:.c=.o)
......
......@@ -514,8 +514,7 @@ int sendImageFrame(camogm_state *state)
int port = state->port_num;
struct timeval start_time, end_time;
D6(fprintf(debug_file, "last_error_code = %d\n", state->last_error_code));
start_time = get_fpga_time(state->fd_fparmsall[port], port);
// start_time = get_fpga_time(state->fd_fparmsall[port], port);
// This is probably needed only for Quicktime (not to exceed already allocated frame index)
if (!state->rawdev_op && (state->frameno >= (state->max_frames))) {
......@@ -646,12 +645,14 @@ int sendImageFrame(camogm_state *state)
/* copy from the beginning of the buffer to the end of the frame */
state->packetchunks[state->chunk_index ].bytes = state->jpeg_len - (state->circ_buff_size[port] - state->cirbuf_rp[port]);
state->packetchunks[state->chunk_index++].chunk = (unsigned char*)&ccam_dma_buf[state->port_num][0];
state->writer_params.segments = 2;
} else { // single segment
D3(fprintf(debug_file, "_11_"));
/* copy from the beginning of the frame to the end of the frame (no buffer rollovers) */
state->packetchunks[state->chunk_index ].bytes = state->jpeg_len;
state->packetchunks[state->chunk_index++].chunk = (unsigned char*)&ccam_dma_buf[state->port_num][state->cirbuf_rp[port] >> 2];
state->writer_params.segments = 1;
}
D3(fprintf(debug_file, "\tcirbuf_rp = 0x%x\t", state->cirbuf_rp[port]));
D3(fprintf(debug_file, "_12_"));
......@@ -687,16 +688,16 @@ int sendImageFrame(camogm_state *state)
}
D3(fprintf(debug_file,"cirbuf_rp to next frame = 0x%x\n", state->cirbuf_rp[port]));
end_time = get_fpga_time(state->fd_fparmsall[port], port);
unsigned int mbps; // write speed, MB/s
unsigned long long time_diff; // time elapsed, in microseconds
time_diff = ((end_time.tv_sec * 1000000 + end_time.tv_usec) - (start_time.tv_sec * 1000000 + start_time.tv_usec));
mbps = ((double)state->rawdev.last_jpeg_size / (double)1048576) / ((double)time_diff / (double)1000000);
D6(fprintf(debug_file, "Frame start time: %ld:%ld; frame end time: %ld:%ld; last frame size: %lu\n",
start_time.tv_sec, start_time.tv_usec,
end_time.tv_sec, end_time.tv_usec,
state->rawdev.last_jpeg_size));
D6(fprintf(debug_file, "Write speed: %d MB/s\n", mbps));
// end_time = get_fpga_time(state->fd_fparmsall[port], port);
// unsigned int mbps; // write speed, MB/s
// unsigned long long time_diff; // time elapsed, in microseconds
// time_diff = ((end_time.tv_sec * 1000000 + end_time.tv_usec) - (start_time.tv_sec * 1000000 + start_time.tv_usec));
// mbps = ((double)state->rawdev.last_jpeg_size / (double)1048576) / ((double)time_diff / (double)1000000);
// D6(fprintf(debug_file, "Frame start time: %ld:%ld; frame end time: %ld:%ld; last frame size: %lu\n",
// start_time.tv_sec, start_time.tv_usec,
// end_time.tv_sec, end_time.tv_usec,
// state->rawdev.last_jpeg_size));
// D6(fprintf(debug_file, "Write speed: %d MB/s\n", mbps));
return 0;
}
......@@ -880,6 +881,9 @@ void get_disk_info(camogm_state *state)
}
if (get_disk_range(state->rawdev.rawdev_path, &rng) == 0) {
state->writer_params.lba_start = rng.from;
state->writer_params.lba_end = rng.to;
state->writer_params.lba_current = state->writer_params.lba_start;
set_disk_range(&rng);
} else {
D0(fprintf(debug_file, "ERROR: unable to get disk size and starting sector\n"));
......
......@@ -27,7 +27,6 @@
#include <elphel/c313a.h>
#include <elphel/x393_devices.h>
#define CAMOGM_FRAME_NOT_READY 1 ///< frame pointer valid, but not yet acquired
#define CAMOGM_FRAME_INVALID 2 ///< invalid frame pointer
#define CAMOGM_FRAME_CHANGED 3 ///< frame parameters have changed
......@@ -150,6 +149,16 @@ struct writer_params {
int last_ret_val; ///< error value return during last frame recording (if any occurred)
bool exit_thread; ///< flag indicating that the writing thread should terminate
int state; ///< the state of disk writing thread
int segments; ///< the number of segments in frame
struct iovec *data_chunks; ///< a set of vectors pointing to aligned frame data buffers
struct iovec prev_rem_vect; ///< vector pointing to the remainder of the previous frame
unsigned char *rem_buff; ///< buffer containing the unaligned remainder of the current frame
unsigned char *prev_rem_buff; ///< buffer containing the unaligned remainder of the previous frame
unsigned char *common_buff; ///< buffer for aligned JPEG header
uint64_t lba_start; ///< disk starting LBA
uint64_t lba_current; ///< current write position in LBAs
uint64_t lba_end; ///< disk last LBA
};
/**
* @struct camogm_state
......
This diff is collapsed.
/** @file camogm_align.h
* @brief Provides frame alignment functions use for recording to block device.
* @copyright Copyright (C) 2017 Elphel, Inc.
*
* @par <b>License</b>
* This program 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.
* This program 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/>.
*/
#ifndef _CAMOGM_ALIGN_H
#define _CAMOGM_ALIGN_H
#include <unistd.h>
#include <sys/types.h>
#include "camogm.h"
#define PHY_BLOCK_SIZE 512 ///< Physical disk block size
#define JPEG_MARKER_LEN 2 ///< The size in bytes of JPEG marker
#define JPEG_SIZE_LEN 2 ///< The size in bytes of JPEG marker length field
#define INCLUDE_REM 1 ///< Include REM buffer to total size calculation
#define EXCLUDE_REM 0 ///< Exclude REM buffer from total size calculation
#define MAX_DATA_CHUNKS 9 ///< An array or JPEG frame chunks contains pointers to JPEG leading marker,
///< JPEG header, Exif data if present, stuffing bytes chunk which aligns
///< the frame size to disk sector boundary, JPEG data which
///< can be split into two chunks, align buffers, JPEG
///< trailing marker, and pointer to a buffer containing the remainder of a
///< frame. Nine chunks of data in total.
#define ALIGNMENT_SIZE 32 ///< Align buffers length to this amount of bytes
/** Common buffer should be large enough to contain JPEG header, Exif, some alignment bytes and remainder from previous frame */
#define COMMON_BUFF_SZ MAX_EXIF_SIZE + JPEG_HEADER_MAXSIZE + ALIGNMENT_SIZE + 2 * PHY_BLOCK_SIZE
#define REM_BUFF_SZ 2 * PHY_BLOCK_SIZE
///** This structure holds raw device buffer pointers */
//struct drv_pointers {
// uint64_t lba_start; ///< raw buffer starting LBA
// uint64_t lba_end; ///< raw buffer ending LBA
// uint64_t lba_write; ///< current write pointer inside raw buffer
// uint16_t wr_count; ///< the number of LBA to write next time
//};
/** Container structure for frame buffers */
//struct frame_buffers {
// struct fvec exif_buff; ///< Exif buffer
// struct fvec jpheader_buff; ///< JPEG header buffer
// struct fvec trailer_buff; ///< buffer for trailing marker
// struct fvec common_buff; ///< common buffer where other parts are combined
// struct fvec rem_buff; ///< remainder from previous frame
//};
/** Symbolic names for slots in buffer pointers. Buffer alignment function relies on the order of these names, so
* new names can be added but the overall order should not be changed */
enum {
CHUNK_LEADER, ///< pointer to JPEG leading marker
CHUNK_EXIF, ///< pointer to Exif buffer
CHUNK_HEADER, ///< pointer to JPEG header data excluding leading marker
CHUNK_COMMON, ///< pointer to common buffer
CHUNK_DATA_0, ///< pointer to JPEG data
CHUNK_DATA_1, ///< pointer to the second half of JPEG data if a frame crosses circbuf boundary
CHUNK_TRAILER, ///< pointer to JPEG trailing marker
CHUNK_ALIGN, ///< pointer to buffer where the second part of JPEG data should be aligned
CHUNK_REM ///< pointer to buffer containing the remainder of current frame. It will be recorded during next transaction
};
int init_align_buffers(camogm_state *state);
void deinit_align_buffers(camogm_state *state);
void align_frame(camogm_state *state);
void reset_chunks(struct iovec *vects, int all);
int update_lba(camogm_state *state);
int get_data_buffers(camogm_state *state, struct iovec *mapped, size_t all_sz);
int prep_last_block(camogm_state *state);
off64_t lba_to_offset(uint64_t lba);
#endif /* _CAMOGM_ALIGN_H */
......@@ -32,12 +32,15 @@
#include "camogm_jpeg.h"
#include "camogm_read.h"
#include "camogm_align.h"
/** State file record format. It includes device path in /dev, starting, current and ending LBAs */
#define STATE_FILE_FORMAT "%s\t%llu\t%llu\t%llu\n"
/* forward declarations */
static void *jpeg_writer(void *thread_args);
static int save_state_file(const rawdev_buffer *rawdev, uint64_t current_pos);
static int open_state_file(const rawdev_buffer *rawdev, uint64_t *current_pos);
/** Get starting and endign LBAs of the partition specified as raw device buffer */
static int get_disk_range(struct range *range)
......@@ -96,12 +99,12 @@ static int find_state(FILE *f, uint64_t *pos, const rawdev_buffer *rawdev)
}
/** Read state from file and restore disk write pointer */
static int open_state_file(const rawdev_buffer *rawdev)
static int open_state_file(const rawdev_buffer *rawdev, uint64_t *current_pos)
{
int fd, len;
FILE *f;
int ret = 0;
uint64_t curr_pos;
uint64_t lba_pos;
char buff[SMALL_BUFF_LEN] = {0};
if (strlen(rawdev->state_path) == 0) {
......@@ -110,15 +113,9 @@ static int open_state_file(const rawdev_buffer *rawdev)
f = fopen(rawdev->state_path, "r");
if (f != NULL) {
if (find_state(f, &curr_pos, rawdev) != -1) {
fd = open(SYSFS_AHCI_LBA_CURRENT, O_WRONLY);
if (fd >= 0) {
len = snprintf(buff, SMALL_BUFF_LEN, "%llu", curr_pos);
write(fd, buff, len + 1);
close(fd);
} else {
ret = -1;
}
if (find_state(f, &lba_pos, rawdev) != -1) {
*current_pos = lba_pos;
D0(fprintf(debug_file, "Got starting LBA from state file: %llu\n", lba_pos));
}
fclose(f);
} else {
......@@ -129,12 +126,11 @@ static int open_state_file(const rawdev_buffer *rawdev)
}
/** Save current position of the disk write pointer */
static int save_state_file(const rawdev_buffer *rawdev)
static int save_state_file(const rawdev_buffer *rawdev, uint64_t current_pos)
{
int ret = 0;
FILE *f;
struct range range;
uint64_t curr_pos;
if (strlen(rawdev->state_path) == 0) {
return ret;
......@@ -143,21 +139,13 @@ static int save_state_file(const rawdev_buffer *rawdev)
return -1;
}
// get raw device buffer current postion on disk, this position indicates where recording has stopped
f = fopen(SYSFS_AHCI_LBA_CURRENT, "r");
if (f == NULL) {
return -1;
}
fscanf(f, "%llu\n", &curr_pos);
fclose(f);
// save pointers to a regular file
f = fopen(rawdev->state_path, "w");
if (f == NULL) {
return -1;
}
fprintf(f, "Device\t\tStart LBA\tCurrent LBA\tEnd LBA\n");
fprintf(f, STATE_FILE_FORMAT, rawdev->rawdev_path, range.from, curr_pos, range.to);
fprintf(f, STATE_FILE_FORMAT, rawdev->rawdev_path, range.from, current_pos, range.to);
fflush(f);
fsync(fileno(f));
fclose(f);
......@@ -198,6 +186,12 @@ int camogm_init_jpeg(camogm_state *state)
D0(fprintf(debug_file, "Can not start writer thread: %s\n", strerror(ret_val)));
ret = -1;
}
ret_val = init_align_buffers(state);
if (ret_val != 0) {
D0(fprintf(debug_file, "Can not initialize alignment buffers\n"));
ret = -1;
}
}
return ret;
......@@ -221,6 +215,8 @@ void camogm_free_jpeg(camogm_state *state)
pthread_mutex_unlock(&state->writer_params.writer_mutex);
pthread_join(state->writer_params.writer_thread, NULL);
state->writer_params.exit_thread = false;
deinit_align_buffers(state);
}
/** Calculate the total length of current frame */
......@@ -247,6 +243,7 @@ int camogm_start_jpeg(camogm_state *state)
{
char * slash;
int rslt;
off64_t offset;
if (!state->rawdev_op) {
strcpy(state->path, state->path_prefix); // make state->path a directory name (will be replaced when the frames will be written)
......@@ -264,16 +261,18 @@ int camogm_start_jpeg(camogm_state *state)
}
}
} else {
// if (open_state_file(&state->rawdev) != 0) {
// D0(fprintf(debug_file, "Could not set write pointer via sysfs, recording will start from the beginning of partition: "
// "%s\n", state->rawdev.rawdev_path));
// }
if (open_state_file(&state->rawdev, &state->writer_params.lba_current) != 0) {
D0(fprintf(debug_file, "Could not get write pointer from state file, recording will start from the beginning of partition: "
"%s\n", state->rawdev.rawdev_path));
}
state->writer_params.blockdev_fd = open(state->rawdev.rawdev_path, O_WRONLY);
if (state->writer_params.blockdev_fd < 0) {
D0(fprintf(debug_file, "Error opening block device: %s\n", state->rawdev.rawdev_path));
return -CAMOGM_FRAME_FILE_ERR;
}
D6(fprintf(debug_file, "Open block device: %s\n", state->rawdev.rawdev_path));
offset = lba_to_offset(state->writer_params.lba_current - state->writer_params.lba_start);
lseek64(state->writer_params.blockdev_fd, offset, SEEK_SET);
D6(fprintf(debug_file, "Open block device: %s, offset in bytes: %llu\n", state->rawdev.rawdev_path, offset));
}
return 0;
......@@ -325,6 +324,15 @@ int camogm_frame_jpeg(camogm_state *state)
while (state->writer_params.data_ready)
pthread_cond_wait(&state->writer_params.main_cond, &state->writer_params.writer_mutex);
D6(fprintf(debug_file, "_13a_"));
D6(fprintf(debug_file, "\n"));
align_frame(state);
if (update_lba(state) == 1) {
D0(fprintf(debug_file, "The end of block device reached, continue recording from start\n"));
}
D6(fprintf(debug_file, "Block device positions: start = %llu, current = %llu, end = %llu\n",
state->writer_params.lba_start, state->writer_params.lba_current, state->writer_params.lba_end));
// proceed if last frame was recorded without errors
if (state->writer_params.last_ret_val == 0) {
state->writer_params.data_ready = true;
......@@ -336,8 +344,8 @@ int camogm_frame_jpeg(camogm_state *state)
}
// update statistics
state->rawdev.last_jpeg_size = camogm_get_jpeg_size(state);
state->rawdev.total_rec_len += state->rawdev.last_jpeg_size;
// state->rawdev.last_jpeg_size = camogm_get_jpeg_size(state);
// state->rawdev.total_rec_len += state->rawdev.last_jpeg_size;
}
return 0;
......@@ -353,15 +361,36 @@ int camogm_frame_jpeg(camogm_state *state)
int camogm_end_jpeg(camogm_state *state)
{
int ret = 0;
int bytes;
ssize_t iovlen;
struct frame_data fdata = {0};
if (state->rawdev_op) {
// write any remaining data, do not use writer thread as there can be only one block left CHUNK_REM buffer
pthread_mutex_lock(&state->writer_params.writer_mutex);
bytes = prep_last_block(state);
if (bytes > 0) {
D6(fprintf(debug_file, "Write last block of data, size = %d\n", bytes));
// the remaining data block is placed in CHUNK_COMMON buffer, write just this buffer
iovlen = writev(state->writer_params.blockdev_fd, &state->writer_params.data_chunks[CHUNK_COMMON], 1);
if (iovlen < bytes) {
D0(fprintf(debug_file, "writev error: %s (returned %i, expected %i)\n", strerror(errno), iovlen, bytes));
state->writer_params.last_ret_val = -CAMOGM_FRAME_FILE_ERR;
} else {
// update statistic, just one block written
state->writer_params.lba_current += 1;
state->rawdev.total_rec_len += bytes;
}
reset_chunks(state->writer_params.data_chunks, 1);
}
pthread_mutex_unlock(&state->writer_params.writer_mutex);
D6(fprintf(debug_file, "Closing block device %s\n", state->rawdev.rawdev_path));
ret = close(state->writer_params.blockdev_fd);
if (ret == -1)
D0(fprintf(debug_file, "Error: %s\n", strerror(errno)));
// save_state_file(&state->rawdev);
save_state_file(&state->rawdev, state->writer_params.lba_current);
}
return ret;
}
......@@ -395,25 +424,40 @@ void *jpeg_writer(void *thread_args)
if (params->data_ready) {
l = 0;
state->writer_params.last_ret_val = 0;
for (int i = 0; i < (state->chunk_index) - 1; i++) {
chunks_iovec[i].iov_base = state->packetchunks[i + 1].chunk;
chunks_iovec[i].iov_len = state->packetchunks[i + 1].bytes;
l += chunks_iovec[i].iov_len;
}
chunk_index = state->chunk_index;
iovlen = writev(state->writer_params.blockdev_fd, chunks_iovec, chunk_index - 1);
if (iovlen < l) {
D0(fprintf(debug_file, "writev error: %s (returned %li, expected %li)\n", strerror(errno), iovlen, l));
// for (int i = 0; i < (state->chunk_index) - 1; i++) {
// chunks_iovec[i].iov_base = state->packetchunks[i + 1].chunk;
// chunks_iovec[i].iov_len = state->packetchunks[i + 1].bytes;
// l += chunks_iovec[i].iov_len;
// }
// chunk_index = state->chunk_index;
chunk_index = get_data_buffers(state, &chunks_iovec, FILE_CHUNKS_NUM);
if (chunk_index > 0) {
for (int i = 0; i < chunk_index; i++)
l += chunks_iovec[i].iov_len;
iovlen = writev(state->writer_params.blockdev_fd, chunks_iovec, chunk_index);
if (iovlen < l) {
D0(fprintf(debug_file, "writev error: %s (returned %i, expected %i)\n", strerror(errno), iovlen, l));
state->writer_params.last_ret_val = -CAMOGM_FRAME_FILE_ERR;
} else {
// update statistic
state->rawdev.last_jpeg_size = l;
state->rawdev.total_rec_len += state->rawdev.last_jpeg_size;
D6(fprintf(debug_file, "Current position in block device: %lld\n", lseek64(state->writer_params.blockdev_fd, 0, SEEK_CUR)));
}
} else {
D0(fprintf(debug_file, "data vector mapping error: %d)\n", chunk_index));
state->writer_params.last_ret_val = -CAMOGM_FRAME_FILE_ERR;
}
// release main thread
reset_chunks(state->writer_params.data_chunks, 0);
params->data_ready = false;
pthread_cond_signal(&params->main_cond);
}
}
params->state = STATE_STOPPED;
pthread_mutex_unlock(&state->writer_params.writer_mutex);
D5(fprintf(debug_file, "Exit from recording thread\n"));
return NULL;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment