Skip to content

Commit

Permalink
Stream mjpeg using avcodec and yuv420p
Browse files Browse the repository at this point in the history
  • Loading branch information
webgeek1234 committed May 31, 2024
1 parent 3374fd9 commit b0e4c57
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 21 deletions.
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
53 changes: 53 additions & 0 deletions src/zm_image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1600,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) > 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
1 change: 1 addition & 0 deletions src/zm_image.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ class Image {

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
71 changes: 59 additions & 12 deletions src/zm_stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,61 @@ StreamBase::~StreamBase() {
delete vid_stream;
delete[] temp_img_buffer;
closeComms();

if (mJpegCodecContext) {
avcodec_free_context(&mJpegCodecContext);
}

if (mJpegSwsContext) {
sws_freeContext(mJpegSwsContext);
}
}

bool StreamBase::initContexts(int p_width, int p_height) {
if (mJpegCodecContext) avcodec_free_context(&mJpegCodecContext);
if (mJpegSwsContext) sws_freeContext(mJpegSwsContext);

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

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

mJpegCodecContext->bit_rate = 400000;
mJpegCodecContext->width = p_width;
mJpegCodecContext->height = p_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 false;
}

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(
monitor->Width(), monitor->Height(), format,
p_width, p_height, AV_PIX_FMT_YUV420P,
SWS_BICUBIC, nullptr, nullptr, nullptr);

return true;
}

bool StreamBase::loadMonitor(int p_monitor_id) {
Expand Down Expand Up @@ -64,7 +119,9 @@ bool StreamBase::loadMonitor(int p_monitor_id) {
return false;
}

return true;
mJpegCodecContext = nullptr;
mJpegSwsContext = nullptr;
return initContexts(monitor->Width(), monitor->Height());
}

bool StreamBase::checkInitialised() {
Expand Down Expand Up @@ -150,9 +207,7 @@ Image *StreamBase::prepareImage(Image *image) {

if (zoom != 100) {
int base_image_width = image->Width(),
base_image_height = image->Height(),
disp_image_width = image->Width() * scale/ZM_SCALE_BASE,
disp_image_height = image->Height() * scale / ZM_SCALE_BASE;
base_image_height = image->Height();
/* x and y are scaled by web UI to base dimensions units.
* When zooming, we blow up the image by the amount 150 for first zoom, right? 150%, then cut out a base sized chunk
* However if we have zoomed before, then we are zooming into the previous cutout
Expand Down Expand Up @@ -229,14 +284,6 @@ Image *StreamBase::prepareImage(Image *image) {
image_copied = true;
}
image->Crop(last_crop);
image->Scale(disp_image_width, disp_image_height);
} else if (scale != ZM_SCALE_BASE) {
Debug(3, "scaling by %d from %dx%d", scale, image->Width(), image->Height());
static Image copy_image;
copy_image.Assign(*image);
image = &copy_image;
image_copied = true;
image->Scale(scale);
}
Debug(3, "Sending %dx%d", image->Width(), image->Height());

Expand Down
4 changes: 4 additions & 0 deletions src/zm_stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ class StreamBase {
uint8_t *temp_img_buffer; // Used when encoding or sending file data
size_t temp_img_buffer_size;

AVCodecContext *mJpegCodecContext;
SwsContext *mJpegSwsContext;

protected:
bool loadMonitor(int monitor_id);
bool checkInitialised();
Expand All @@ -161,6 +164,7 @@ class StreamBase {
void checkCommandQueue();
virtual void processCommand(const CmdMsg *msg)=0;
void reserveTempImgBuffer(size_t size);
bool initContexts(int p_width, int p_height);

public:
StreamBase():
Expand Down

0 comments on commit b0e4c57

Please sign in to comment.