diff --git a/src/zm_camera.cpp b/src/zm_camera.cpp index ddece60004f..885f90c754e 100644 --- a/src/zm_camera.cpp +++ b/src/zm_camera.cpp @@ -67,6 +67,42 @@ Camera::Camera( Debug(2, "New camera id: %d width: %d line size: %d height: %d colours: %d subpixelorder: %d capture: %d, size: %llu", monitor->Id(), width, linesize, height, colours, subpixelorder, capture, imagesize); + + const AVCodec* mJpegCodec = avcodec_find_encoder(AV_CODEC_ID_MJPEG); + if (!mJpegCodec) { + Error("MJPEG codec not found"); + return; + } + + mJpegCodecContext = avcodec_alloc_context3(mJpegCodec); + if (!mJpegCodecContext) { + Error("Could not allocate jpeg codec context"); + return; + } + + mJpegCodecContext->bit_rate = 400000; + mJpegCodecContext->width = width; + mJpegCodecContext->height = 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; + } + + 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); } Camera::~Camera() { @@ -82,6 +118,16 @@ Camera::~Camera() { } mVideoStream = nullptr; mAudioStream = nullptr; + + if (mJpegCodecContext) { + avcodec_close(mJpegCodecContext); + avcodec_free_context(&mJpegCodecContext); + mJpegCodecContext = nullptr; + } + + if (mJpegSwsContext) { + sws_freeContext(mJpegSwsContext); + } } AVStream *Camera::getVideoStream() { diff --git a/src/zm_camera.h b/src/zm_camera.h index c035fccc81e..327c422a2db 100644 --- a/src/zm_camera.h +++ b/src/zm_camera.h @@ -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 @@ -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; }; diff --git a/src/zm_image.cpp b/src/zm_image.cpp index 26e69644c07..dc462784f43 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -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) @@ -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; @@ -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 @@ -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 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); @@ -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(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(×tamp_t, ×tamp_tm)); - Seconds ts_sec = std::chrono::duration_cast(timestamp.time_since_epoch()); - Microseconds ts_usec = std::chrono::duration_cast(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(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 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); + + pkt = av_packet_alloc(); + + avcodec_send_frame(mJpegCodecContext, frame.get()); + if (avcodec_receive_packet(mJpegCodecContext, pkt) == 0) { + fwrite(pkt->data, 1, pkt->size, outfile); + av_packet_free(&pkt); + } + + av_frame_unref(frame.get()); fl.l_type = F_UNLCK; /* set to unlock same region */ if (fcntl(raw_fd, F_SETLK, &fl) == -1) { diff --git a/src/zm_image.h b/src/zm_image.h index 566e2477b2e..91f87d420d8 100644 --- a/src/zm_image.h +++ b/src/zm_image.h @@ -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; @@ -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, @@ -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); diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 9896cedb2f9..18b3d0b7055 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -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; @@ -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())) {