Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write jpegs and stream mjpeg using avcodec #3970

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 52 additions & 8 deletions src/zm_event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,46 @@ Event::Event(
id = zmDbDoInsert(sql);
} while (!id and !zm_terminate);

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 = monitor->Width();
mJpegCodecContext->height = monitor->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;
switch (monitor->Colours()) {
case ZM_COLOUR_RGB24:
format = (monitor->SubpixelOrder() == ZM_SUBPIX_ORDER_BGR ? AV_PIX_FMT_BGR24 : AV_PIX_FMT_RGB24);
break;
case ZM_COLOUR_GRAY8:
format = AV_PIX_FMT_GRAY8;
break;
default:
format = AV_PIX_FMT_RGBA;
break;
};
mJpegSwsContext = sws_getContext(
mJpegCodecContext->width, mJpegCodecContext->height, format,
mJpegCodecContext->width, mJpegCodecContext->height, AV_PIX_FMT_YUV420P,
SWS_BICUBIC, nullptr, nullptr, nullptr);

thread_ = std::thread(&Event::Run, this);
}

Expand Down Expand Up @@ -212,6 +252,16 @@ Event::~Event() {
id);
zmDbDoUpdate(sql);
} // end if no changed rows due to Name change during recording

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

if (mJpegSwsContext) {
sws_freeContext(mJpegSwsContext);
}
} // Event::~Event()

void Event::createNotes(std::string &notes) {
Expand All @@ -233,23 +283,17 @@ void Event::addNote(const char *cause, const std::string &note) {
}

bool Event::WriteFrameImage(Image *image, SystemTimePoint timestamp, const char *event_file, bool alarm_frame) const {
int thisquality =
(alarm_frame && (config.jpeg_alarm_file_quality > config.jpeg_file_quality)) ?
config.jpeg_alarm_file_quality : 0; // quality to use, zero is default

bool rc;

SystemTimePoint jpeg_timestamp = monitor->Exif() ? timestamp : SystemTimePoint();

if (!config.timestamp_on_capture) {
// stash the image we plan to use in another pointer regardless if timestamped.
// exif is only timestamp at present this switches on or off for write
Image *ts_image = new Image(*image);
monitor->TimestampImage(ts_image, timestamp);
rc = ts_image->WriteJpeg(event_file, thisquality, jpeg_timestamp);
rc = ts_image->WriteJpeg(event_file, mJpegCodecContext, mJpegSwsContext);
delete ts_image;
} else {
rc = image->WriteJpeg(event_file, thisquality, jpeg_timestamp);
rc = image->WriteJpeg(event_file, mJpegCodecContext, mJpegSwsContext);
}

return rc;
Expand Down
3 changes: 3 additions & 0 deletions src/zm_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ class Event {
std::string alarm_file;
VideoStore *videoStore;

AVCodecContext *mJpegCodecContext;
SwsContext *mJpegSwsContext;

std::string container;
std::string codec;
std::string video_file;
Expand Down
9 changes: 7 additions & 2 deletions src/zm_eventstream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -870,14 +870,19 @@ bool EventStream::sendFrame(Microseconds delta_us) {
}

Image *send_image = prepareImage(image);
reserveTempImgBuffer(send_image->Size());
int l_width = floor(send_image->Width() * scale / ZM_SCALE_BASE);
int l_height = floor(send_image->Height() * scale / ZM_SCALE_BASE);
reserveTempImgBuffer(av_image_get_buffer_size(AV_PIX_FMT_YUVJ420P, l_width, l_height, 32));
int img_buffer_size = 0;
uint8_t *img_buffer = temp_img_buffer;

fprintf(stdout, "--" BOUNDARY "\r\n");
switch ( type ) {
case STREAM_JPEG :
send_image->EncodeJpeg(img_buffer, &img_buffer_size);
if (mJpegCodecContext->width != l_width || mJpegCodecContext->height != l_height) {
initContexts(l_width, l_height);
}
send_image->EncodeJpeg(img_buffer, &img_buffer_size, mJpegCodecContext, mJpegSwsContext);
fputs("Content-Type: image/jpeg\r\n", stdout);
break;
case STREAM_ZIP :
Expand Down
126 changes: 125 additions & 1 deletion 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 @@ -1321,6 +1321,77 @@ bool Image::WriteJpeg(const std::string &filename,
return true;
}

bool Image::WriteJpeg(const std::string &filename,
AVCodecContext *p_jpegcodeccontext,
SwsContext *p_jpegswscontext) const {

if (config.colour_jpeg_files && (colours == ZM_COLOUR_GRAY8)) {
Image temp_image(*this);
temp_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB);
return temp_image.WriteJpeg(filename, p_jpegcodeccontext, p_jpegswscontext);
}

if (p_jpegcodeccontext == NULL) {
Error("Jpeg codec context is not initialized");
return false;
}

FILE *outfile = nullptr;
int raw_fd = 0;
av_frame_ptr frame = av_frame_ptr{zm_av_frame_alloc()};
AVPacket *pkt;

raw_fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (raw_fd < 0)
return false;
outfile = fdopen(raw_fd, "wb");
if (outfile == nullptr) {
close(raw_fd);
return false;
}

struct flock fl = { F_WRLCK, SEEK_SET, 0, 0, 0 };
if (fcntl(raw_fd, F_SETLKW, &fl) == -1) {
Error("Couldn't get lock on %s, continuing", filename.c_str());
}

if ( p_jpegswscontext ) {
av_frame_ptr temp_frame = av_frame_ptr{zm_av_frame_alloc()};
PopulateFrame(temp_frame.get());

frame.get()->width = width;
frame.get()->height = height;
frame.get()->format = AV_PIX_FMT_YUV420P;
av_image_fill_linesizes(frame.get()->linesize, AV_PIX_FMT_YUV420P, width);
av_frame_get_buffer(frame.get(), 32);

sws_scale(p_jpegswscontext, temp_frame.get()->data, temp_frame.get()->linesize, 0, height, frame.get()->data, frame.get()->linesize);

av_frame_unref(temp_frame.get());
} else {
PopulateFrame(frame.get());
}

pkt = av_packet_alloc();

avcodec_send_frame(p_jpegcodeccontext, frame.get());
if (avcodec_receive_packet(p_jpegcodeccontext, 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) {
Error("Failed to unlock %s", filename.c_str());
}

fclose(outfile);

return true;
}

bool Image::DecodeJpeg(const JOCTET *inbuffer, int inbuffer_size, unsigned int p_colours, unsigned int p_subpixelorder) {
unsigned int new_width, new_height, new_colours, new_subpixelorder;

Expand Down Expand Up @@ -1529,6 +1600,59 @@ bool Image::EncodeJpeg(JOCTET *outbuffer, int *outbuffer_size, int quality_overr
return true;
}

bool Image::EncodeJpeg(JOCTET *outbuffer, int *outbuffer_size, AVCodecContext *p_jpegcodeccontext, SwsContext *p_jpegswscontext) const {
if ( config.colour_jpeg_files && (colours == ZM_COLOUR_GRAY8) ) {
Image temp_image(*this);
temp_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB);
return temp_image.EncodeJpeg(outbuffer, outbuffer_size, p_jpegcodeccontext, p_jpegswscontext);
}

if (p_jpegcodeccontext == NULL) {
Error("Jpeg codec context is not initialized");
return false;
}

std::unique_lock<std::mutex> lck(jpeg_mutex);

av_frame_ptr frame = av_frame_ptr{zm_av_frame_alloc()};
AVPacket *pkt;

if (av_image_get_buffer_size(AV_PIX_FMT_YUVJ420P, width, height, 32) > static_cast<int>(Size())) {
Error("Output buffer not large enough");
return false;
}

if ( p_jpegswscontext ) {
av_frame_ptr temp_frame = av_frame_ptr{zm_av_frame_alloc()};
PopulateFrame(temp_frame.get());

frame.get()->width = width;
frame.get()->height = height;
frame.get()->format = AV_PIX_FMT_YUV420P;
av_image_fill_linesizes(frame.get()->linesize, AV_PIX_FMT_YUV420P, width);
av_frame_get_buffer(frame.get(), 32);

sws_scale(p_jpegswscontext, temp_frame.get()->data, temp_frame.get()->linesize, 0, height, frame.get()->data, frame.get()->linesize);

av_frame_unref(temp_frame.get());
} else {
PopulateFrame(frame.get());
}

pkt = av_packet_alloc();

avcodec_send_frame(p_jpegcodeccontext, frame.get());
if (avcodec_receive_packet(p_jpegcodeccontext, pkt) == 0) {
memcpy(outbuffer, pkt->data, pkt->size);
*outbuffer_size = pkt->size;
}

av_packet_free(&pkt);
av_frame_unref(frame.get());

return true;
}

#if HAVE_ZLIB_H
bool Image::Unzip( const Bytef *inbuffer, unsigned long inbuffer_size ) {
unsigned long zip_size = size;
Expand Down
6 changes: 5 additions & 1 deletion src/zm_image.h
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,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 Expand Up @@ -235,9 +235,13 @@ class Image {
const int &quality_override,
SystemTimePoint timestamp,
bool on_blocking_abort) const;
bool WriteJpeg(const std::string &filename,
AVCodecContext *p_jpegcodeccontext,
SwsContext *p_jpegswscontext) const;

bool DecodeJpeg(const JOCTET *inbuffer, int inbuffer_size, unsigned int p_colours, unsigned int p_subpixelorder);
bool EncodeJpeg(JOCTET *outbuffer, int *outbuffer_size, int quality_override=0) const;
bool EncodeJpeg(JOCTET *outbuffer, int *outbuffer_size, AVCodecContext *p_jpegcodeccontext, SwsContext *p_jpegswscontext) const;

#if HAVE_ZLIB_H
bool Unzip(const Bytef *inbuffer, unsigned long inbuffer_size);
Expand Down
20 changes: 13 additions & 7 deletions src/zm_monitorstream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -393,14 +393,20 @@ bool MonitorStream::sendFrame(Image *image, SystemTimePoint timestamp) {

/* double pts = */ vid_stream->EncodeFrame(send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_time.count());
} else {
reserveTempImgBuffer(send_image->Size());
int l_width = floor(send_image->Width() * scale / ZM_SCALE_BASE);
int l_height = floor(send_image->Height() * scale / ZM_SCALE_BASE);

reserveTempImgBuffer(av_image_get_buffer_size(AV_PIX_FMT_YUVJ420P, l_width, l_height, 32));

int img_buffer_size = 0;
unsigned char *img_buffer = temp_img_buffer;

switch (type) {
case STREAM_JPEG :
send_image->EncodeJpeg(img_buffer, &img_buffer_size);
if (mJpegCodecContext->width != l_width || mJpegCodecContext->height != l_height) {
initContexts(l_width, l_height);
}
send_image->EncodeJpeg(img_buffer, &img_buffer_size, mJpegCodecContext, mJpegSwsContext);
fputs("Content-Type: image/jpeg\r\n", stdout);
break;
case STREAM_RAW :
Expand Down Expand Up @@ -937,12 +943,12 @@ void MonitorStream::SingleImage(int scale) {
SystemTimePoint(zm::chrono::duration_cast<Microseconds>(monitor->shared_timestamps[index])));
}

if ( scale != ZM_SCALE_BASE ) {
scaled_image.Assign(*snap_image);
scaled_image.Scale(scale);
snap_image = &scaled_image;
int l_width = floor(snap_image->Width() * scale / ZM_SCALE_BASE);
int l_height = floor(snap_image->Height() * scale / ZM_SCALE_BASE);
if (mJpegCodecContext->width != l_width || mJpegCodecContext->height != l_height) {
initContexts(l_width, l_height);
}
snap_image->EncodeJpeg(img_buffer, &img_buffer_size);
snap_image->EncodeJpeg(img_buffer, &img_buffer_size, mJpegCodecContext, mJpegSwsContext);

fprintf(stdout,
"Content-Length: %d\r\n"
Expand Down
Loading
Loading