Skip to content

Commit

Permalink
Write jpegs using avcodec
Browse files Browse the repository at this point in the history
  • Loading branch information
webgeek1234 committed Apr 19, 2024
1 parent b800a91 commit a565c08
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 157 deletions.
2 changes: 2 additions & 0 deletions src/zm_camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ Camera::Camera(
mAudioStreamId(-1),
mVideoCodecContext(nullptr),
mAudioCodecContext(nullptr),
mJpegCodecContext(nullptr),
mJpegSwsContext(nullptr),
mVideoStream(nullptr),
mAudioStream(nullptr),
mFormatContext(nullptr),
Expand Down
4 changes: 4 additions & 0 deletions src/zm_camera.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class Camera {
int mAudioStreamId;
AVCodecContext *mVideoCodecContext;
AVCodecContext *mAudioCodecContext;
AVCodecContext *mJpegCodecContext;
SwsContext *mJpegSwsContext;
AVStream *mVideoStream;
AVStream *mAudioStream;
AVFormatContext *mFormatContext; // One for video, one for audio
Expand Down Expand Up @@ -127,6 +129,8 @@ class Camera {
virtual AVStream *getAudioStream() { return mAudioStream; };
virtual AVCodecContext *getVideoCodecContext() { return mVideoCodecContext; };
virtual AVCodecContext *getAudioCodecContext() { return mAudioCodecContext; };
virtual AVCodecContext *getJpegCodecContext() { return mJpegCodecContext; };
virtual SwsContext *getJpegSwsContext() { return mJpegSwsContext; };
int getVideoStreamId() { return mVideoStreamId; };
int getAudioStreamId() { return mAudioStreamId; };

Expand Down
46 changes: 46 additions & 0 deletions src/zm_ffmpeg_camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,42 @@ int FfmpegCamera::OpenFfmpeg() {
}
} // end if have audio stream

const AVCodec* mJpegCodec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
if (!mJpegCodec) {
Error("MJPEG codec not found");
return -1;
}

mJpegCodecContext = avcodec_alloc_context3(mJpegCodec);
if (!mJpegCodecContext) {
Error("Could not allocate jpeg codec context");
return -1;
}

mJpegCodecContext->bit_rate = 400000;
mJpegCodecContext->width = mFormatContext->streams[mVideoStreamId]->codecpar->width;
mJpegCodecContext->height = mFormatContext->streams[mVideoStreamId]->codecpar->height;
mJpegCodecContext->time_base= (AVRational) {1,25};
mJpegCodecContext->pix_fmt = AV_PIX_FMT_YUVJ420P;

if (avcodec_open2(mJpegCodecContext, mJpegCodec, NULL) < 0) {
Error("Could not open mjpeg codec");
return -1;
}

AVPixelFormat format;
if ( colours == ZM_COLOUR_RGB24 ) {
format = (subpixelorder == ZM_SUBPIX_ORDER_BGR ? AV_PIX_FMT_BGR24 : AV_PIX_FMT_RGB24);
} else if ( colours == ZM_COLOUR_GRAY8 ) {
format = AV_PIX_FMT_GRAY8;
} else {
format = AV_PIX_FMT_RGBA;
}
mJpegSwsContext = sws_getContext(
width, height, format,
width, height, AV_PIX_FMT_YUV420P,
SWS_BICUBIC, nullptr, nullptr, nullptr);

if (
((unsigned int)mVideoCodecContext->width != width)
||
Expand Down Expand Up @@ -615,6 +651,16 @@ int FfmpegCamera::Close() {
mAudioCodecContext = nullptr;
}

if (mJpegCodecContext) {
avcodec_close(mJpegCodecContext);
avcodec_free_context(&mJpegCodecContext);
mJpegCodecContext = nullptr;
}

if (mJpegSwsContext) {
sws_freeContext(mJpegSwsContext);
}

#if HAVE_LIBAVUTIL_HWCONTEXT_H
if ( hw_device_ctx ) {
av_buffer_unref(&hw_device_ctx);
Expand Down
186 changes: 30 additions & 156 deletions src/zm_image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ Image::Image(const AVFrame *frame, int p_width, int p_height) :
static void dont_free(void *opaque, uint8_t *data) {
}

int Image::PopulateFrame(AVFrame *frame) {
int Image::PopulateFrame(AVFrame *frame) const {
Debug(1, "PopulateFrame: width %d height %d linesize %d colours %d imagesize %d %s",
width, height, linesize, colours, size,
av_get_pix_fmt_name(imagePixFormat)
Expand Down Expand Up @@ -343,6 +343,7 @@ bool Image::Assign(const AVFrame *frame, SwsContext *convert_context, AVFrame *t
Image::Image(const Image &p_image) {
if ( !initialised )
Initialise();
mJpegCodecContext = p_image.mJpegCodecContext;
width = p_image.width;
linesize = p_image.linesize;
padding = 0;
Expand Down Expand Up @@ -787,6 +788,8 @@ void Image::Assign(const Image &image) {
return;
}

mJpegCodecContext = image.mJpegCodecContext;

if ( !buffer
|| image.width != width || image.height != height
|| image.colours != colours || image.subpixelorder != subpixelorder
Expand Down Expand Up @@ -1127,39 +1130,15 @@ bool Image::WriteJpeg(const std::string &filename,
return temp_image.WriteJpeg(filename, quality_override, timestamp, on_blocking_abort);
}

// jpeg libs are not thread safe
std::unique_lock<std::mutex> lck(jpeg_mutex);

int quality = quality_override ? quality_override : config.jpeg_file_quality;
if (mJpegCodecContext == NULL) {
Error("Jpeg codec context is not initialized");
return false;
}

jpeg_compress_struct *cinfo = writejpg_ccinfo[quality];
FILE *outfile = nullptr;
int raw_fd = 0;

if (!cinfo) {
cinfo = writejpg_ccinfo[quality] = new jpeg_compress_struct;
cinfo->err = jpeg_std_error(&jpg_err.pub);
jpeg_create_compress(cinfo);
}
if (!on_blocking_abort) {
jpg_err.pub.error_exit = zm_jpeg_error_exit;
jpg_err.pub.emit_message = zm_jpeg_emit_message;
} else {
jpg_err.pub.error_exit = zm_jpeg_error_silent;
jpg_err.pub.emit_message = zm_jpeg_emit_silence;
if (setjmp(jpg_err.setjmp_buffer)) {
jpeg_abort_compress(cinfo);
Debug(1,
"Aborted a write mid-stream and %s and %d",
(outfile == nullptr) ? "closing file" : "file not opened",
raw_fd);
if (raw_fd)
close(raw_fd);
if (outfile)
fclose(outfile);
return false;
}
}
av_frame_ptr frame = av_frame_ptr{zm_av_frame_alloc()};
AVPacket pkt;

if (!on_blocking_abort) {
raw_fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
Expand All @@ -1180,136 +1159,31 @@ bool Image::WriteJpeg(const std::string &filename,
Error("Couldn't get lock on %s, continuing", filename.c_str());
}

jpeg_stdio_dest(cinfo, outfile);
if ( mJpegSwsContext ) {
av_frame_ptr temp_frame = av_frame_ptr{zm_av_frame_alloc()};
PopulateFrame(temp_frame.get());

cinfo->image_width = width; /* image width and height, in pixels */
cinfo->image_height = height;
frame.get()->width = width;
frame.get()->height = height;
frame.get()->format = AV_PIX_FMT_YUV420P;
av_frame_get_buffer(frame.get(), 32);

switch (colours) {
case ZM_COLOUR_GRAY8:
cinfo->input_components = 1;
cinfo->in_color_space = JCS_GRAYSCALE;
break;
case ZM_COLOUR_RGB32:
#ifdef JCS_EXTENSIONS
cinfo->input_components = 4;
if (subpixelorder == ZM_SUBPIX_ORDER_RGBA) {
cinfo->in_color_space = JCS_EXT_RGBX;
} else if (subpixelorder == ZM_SUBPIX_ORDER_BGRA) {
cinfo->in_color_space = JCS_EXT_BGRX;
} else if (subpixelorder == ZM_SUBPIX_ORDER_ARGB) {
cinfo->in_color_space = JCS_EXT_XRGB;
} else if (subpixelorder == ZM_SUBPIX_ORDER_ABGR) {
cinfo->in_color_space = JCS_EXT_XBGR;
} else {
Warning("Unknown subpixelorder %d", subpixelorder);
/* Assume RGBA */
cinfo->in_color_space = JCS_EXT_RGBX;
}
break;
#else
Error("libjpeg-turbo is required for JPEG encoding directly from RGB32 source");
jpeg_abort_compress(cinfo);
fl.l_type = F_UNLCK;
fcntl(raw_fd, F_SETLK, &fl);
fclose(outfile);
return false;
#endif
case ZM_COLOUR_RGB24:
default:
cinfo->input_components = 3;
if (subpixelorder == ZM_SUBPIX_ORDER_BGR) {
#ifdef JCS_EXTENSIONS
cinfo->in_color_space = JCS_EXT_BGR;
#else
Error("libjpeg-turbo is required for JPEG encoding directly from BGR24 source");
jpeg_abort_compress(cinfo);
fl.l_type = F_UNLCK;
fcntl(raw_fd, F_SETLK, &fl);
fclose(outfile);
return false;
#endif
} else if (subpixelorder == ZM_SUBPIX_ORDER_YUV420P) {
cinfo->in_color_space = JCS_YCbCr;
} else {
/* Assume RGB */
/*
#ifdef JCS_EXTENSIONS
cinfo->out_color_space = JCS_EXT_RGB;
#else
cinfo->out_color_space = JCS_RGB;
#endif
*/
cinfo->in_color_space = JCS_RGB;
}
break;
} // end switch(colours)

jpeg_set_defaults(cinfo);
jpeg_set_quality(cinfo, quality, FALSE);
cinfo->dct_method = JDCT_FASTEST;
sws_scale(mJpegSwsContext, temp_frame.get()->data, temp_frame.get()->linesize, 0, height, frame.get()->data, frame.get()->linesize);

jpeg_start_compress(cinfo, TRUE);
if (config.add_jpeg_comments && !annotation_.empty()) {
jpeg_write_marker(cinfo, JPEG_COM, reinterpret_cast<const JOCTET *>(annotation_.c_str()), annotation_.size());
}
// If we have a non-zero time (meaning a parameter was passed in), then form a simple exif segment with that time as DateTimeOriginal and SubsecTimeOriginal
// No timestamp just leave off the exif section.
if (timestamp.time_since_epoch() > Seconds(0)) {
#define EXIFTIMES_MS_OFFSET 0x36 // three decimal digits for milliseconds
#define EXIFTIMES_MS_LEN 0x03
#define EXIFTIMES_OFFSET 0x3E // 19 characters format '2015:07:21 13:14:45' not including quotes
#define EXIFTIMES_LEN 0x13 // = 19
#define EXIF_CODE 0xE1

// This is a lot of stuff to allocate on the stack. Recommend char *timebuf[64];
char timebuf[64], msbuf[64];

tm timestamp_tm = {};
time_t timestamp_t = std::chrono::system_clock::to_time_t(timestamp);
strftime(timebuf, sizeof timebuf, "%Y:%m:%d %H:%M:%S", localtime_r(&timestamp_t, &timestamp_tm));
Seconds ts_sec = std::chrono::duration_cast<Seconds>(timestamp.time_since_epoch());
Microseconds ts_usec = std::chrono::duration_cast<Microseconds>(timestamp.time_since_epoch() - ts_sec);
// we only use milliseconds because that's all defined in exif, but this is the whole microseconds because we have it
snprintf(msbuf, sizeof msbuf, "%06d", static_cast<int32>(ts_usec.count()));

unsigned char exiftimes[82] = {
0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00,
0x69, 0x87, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x03, 0x90, 0x02, 0x00, 0x14, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x91, 0x92,
0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00
};
memcpy(&exiftimes[EXIFTIMES_OFFSET], timebuf, EXIFTIMES_LEN);
memcpy(&exiftimes[EXIFTIMES_MS_OFFSET], msbuf, EXIFTIMES_MS_LEN);
jpeg_write_marker(cinfo, EXIF_CODE, (const JOCTET *) exiftimes, sizeof(exiftimes));
}

if (subpixelorder == ZM_SUBPIX_ORDER_YUV420P) {
std::vector<uint8_t> tmprowbuf(width * 3);
JSAMPROW row_pointer = &tmprowbuf[0]; /* pointer to a single row */
while (cinfo->next_scanline < cinfo->image_height) {
unsigned i, j;
unsigned offset = cinfo->next_scanline * cinfo->image_width * 2; //offset to the correct row
for (i = 0, j = 0; i < cinfo->image_width * 2; i += 4, j += 6) { //input strides by 4 bytes, output strides by 6 (2 pixels)
tmprowbuf[j + 0] = buffer[offset + i + 0]; // Y (unique to this pixel)
tmprowbuf[j + 1] = buffer[offset + i + 1]; // U (shared between pixels)
tmprowbuf[j + 2] = buffer[offset + i + 3]; // V (shared between pixels)
tmprowbuf[j + 3] = buffer[offset + i + 2]; // Y (unique to this pixel)
tmprowbuf[j + 4] = buffer[offset + i + 1]; // U (shared between pixels)
tmprowbuf[j + 5] = buffer[offset + i + 3]; // V (shared between pixels)
}
jpeg_write_scanlines(cinfo, &row_pointer, 1);
}
av_frame_unref(temp_frame.get());
} else {
JSAMPROW row_pointer = buffer; /* pointer to a single row */
while (cinfo->next_scanline < cinfo->image_height) {
jpeg_write_scanlines(cinfo, &row_pointer, 1);
row_pointer += linesize;
}
PopulateFrame(frame.get());
}
jpeg_finish_compress(cinfo);

av_new_packet(&pkt, av_image_get_buffer_size(AVPixFormat(), width, height, 32));

avcodec_send_frame(mJpegCodecContext, frame.get());
if (avcodec_receive_packet(mJpegCodecContext, &pkt) == 0) {
fwrite(pkt.data, 1, pkt.size, outfile);
av_packet_unref(&pkt);
}

av_frame_unref(frame.get());

fl.l_type = F_UNLCK; /* set to unlock same region */
if (fcntl(raw_fd, F_SETLK, &fl) == -1) {
Expand Down
6 changes: 5 additions & 1 deletion src/zm_image.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ class Image {
static jpeg_decompress_struct *readjpg_dcinfo;
static jpeg_decompress_struct *decodejpg_dcinfo;
static struct zm_error_mgr jpg_err;
AVCodecContext *mJpegCodecContext;
SwsContext *mJpegSwsContext;

unsigned int width;
unsigned int linesize;
Expand Down Expand Up @@ -188,6 +190,8 @@ class Image {
width = linesize = height = colours = size = pixels = subpixelorder = 0;
}

inline void SetJpegContexts(AVCodecContext *p_jpegcodeccontext, SwsContext *p_jpegswscontext) { mJpegCodecContext = p_jpegcodeccontext; mJpegSwsContext = p_jpegswscontext; }

void Assign(
unsigned int p_width,
unsigned int p_height,
Expand All @@ -207,7 +211,7 @@ class Image {
const size_t buffer_size,
const int p_buffertype);

int PopulateFrame(AVFrame *frame);
int PopulateFrame(AVFrame *frame) const;

inline void CopyBuffer(const Image &image) {
Assign(image);
Expand Down
2 changes: 2 additions & 0 deletions src/zm_monitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2762,6 +2762,7 @@ int Monitor::Capture() {
/* HTML colour code is actually BGR in memory, we want RGB */
signalcolor = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR);
Image *capture_image = new Image(width, height, camera->Colours(), camera->SubpixelOrder());
capture_image->SetJpegContexts(camera->getJpegCodecContext(), camera->getJpegSwsContext());
capture_image->Fill(signalcolor);
shared_data->signal = false;
shared_data->last_write_index = index;
Expand Down Expand Up @@ -2931,6 +2932,7 @@ bool Monitor::Decode() {
if (ret > 0 and !zm_terminate) {
if (packet->in_frame and !packet->image) {
packet->image = new Image(camera_width, camera_height, camera->Colours(), camera->SubpixelOrder());
packet->image->SetJpegContexts(camera->getJpegCodecContext(), camera->getJpegSwsContext());

if (convert_context || this->setupConvertContext(packet->in_frame.get(), packet->image)) {
if (!packet->image->Assign(packet->in_frame.get(), convert_context, dest_frame.get())) {
Expand Down

0 comments on commit a565c08

Please sign in to comment.