Creating MJEPG video from multiple JPEG encoded images without using cv::imdecode() - c++

I need to store multiple encoded frames in memory.
I am using cv::imencode(".jpg", ...) for compressing and storing the encoded images to std::list<std::vector<u_char>> compressed_images - a list of compressed images.
I want to create a video from compressed_images, but I must use cv::imdecode() to decode all the images to cv::Mat, then use cv::VideoWriter to save the images to MJPEG video.
Can I skip cv::imdecode(), or use other solution for avoid encoding two times?

You may PIPE the encoded images to FFmpeg.
According to the following post, you can "simply mux the JEPG images to make a video".
In case the frames are in memory, you can write the encoded images to an input PIPE of FFmpeg.
Instead of -f image2, use -f image2pipe format flag.
Implementing the solution in C++ is too difficult for me.
I have implemented a Python code sample.
The code sample:
Builds a list of 100 encoded frames (green frame counter).
PIPE the encoded frames to ffmpeg sub-process.
The encoded images are written to stdin input stream of the sub-process.
Here is the Python code sample:
import numpy as np
import cv2
import subprocess as sp
# Generate 100 synthetic JPEG encoded images in memory:
###############################################################################
# List of JPEG encoded frames.
jpeg_frames = []
width, height, n_frames = 640, 480, 100 # 100 frames, resolution 640x480
for i in range(n_frames):
img = np.full((height, width, 3), 60, np.uint8)
cv2.putText(img, str(i+1), (width//2-100*len(str(i+1)), height//2+100), cv2.FONT_HERSHEY_DUPLEX, 10, (30, 255, 30), 20) # Green number
# JPEG Encode img into jpeg_img
_, jpeg_img = cv2.imencode('.JPEG', img)
# Append encoded image to list.
jpeg_frames.append(jpeg_img)
###############################################################################
#FFmpeg input PIPE: JPEG encoded images
#FFmpeg output AVI file encoded with MJPEG codec.
# https://video.stackexchange.com/questions/7903/how-to-losslessly-encode-a-jpg-image-sequence-to-a-video-in-ffmpeg
process = sp.Popen('ffmpeg -y -f image2pipe -r 10 -i pipe: -codec copy out.avi', stdin=sp.PIPE)
# Iterate list of encoded frames and write the encoded frames to process.stdin
for jpeg_img in jpeg_frames:
process.stdin.write(jpeg_img)
# Close and flush stdin
process.stdin.close()
# Wait one more second and terminate the sub-process
try:
process.wait(1)
except (sp.TimeoutExpired):
process.kill()
Update: C++ implementation:
#include "opencv2/opencv.hpp"
#include "opencv2/highgui.hpp"
#ifdef _MSC_VER
#include <Windows.h> //For Sleep(1000)
#endif
#include <stdio.h>
int main()
{
int width = 640;
int height = 480;
int n_frames = 100;
//Generate 100 synthetic JPEG encoded images in memory:
//////////////////////////////////////////////////////////////////////////
std::list<std::vector<uchar>> jpeg_frames;
for (int i = 0; i < n_frames; i++)
{
cv::Mat img = cv::Mat(height, width, CV_8UC3);
img = cv::Scalar(60, 60, 60);
cv::putText(img, std::to_string(i + 1), cv::Point(width / 2 - 100 * (int)(std::to_string(i + 1).length()), height / 2 + 100), cv::FONT_HERSHEY_DUPLEX, 10, cv::Scalar(30, 255, 30), 20); // Green number
//cv::imshow("img", img);cv::waitKey(1);
std::vector<uchar> jpeg_img;
cv::imencode(".JPEG", img, jpeg_img);
jpeg_frames.push_back(jpeg_img);
}
//////////////////////////////////////////////////////////////////////////
//In Windows (using Visual Studio) we need to use _popen and in Linux popen
#ifdef _MSC_VER
//ffmpeg.exe must be in the system path (or in the working directory)
FILE *pipeout = _popen("ffmpeg -y -f image2pipe -r 10 -i pipe: -codec copy out.avi", "wb"); //For Windows use "wb"
#else
//https://batchloaf.wordpress.com/2017/02/12/a-simple-way-to-read-and-write-audio-and-video-files-in-c-using-ffmpeg-part-2-video/
FILE *pipeout = popen("ffmpeg -y -f image2pipe -r 10 -i pipe: -codec copy out.avi", "w"); //For Linux use "w"
//In case ffmpeg is not in the execution path, you may use full path:
//popen("/usr/bin/ffmpeg -y -f image2pipe -r 10 -i pipe: -codec copy out.avi", "w");
#endif
std::list<std::vector<uchar>>::iterator it;
//Iterate list of encoded frames and write the encoded frames to pipeout
for (it = jpeg_frames.begin(); it != jpeg_frames.end(); ++it)
{
std::vector<uchar> jpeg_img = *it;
// Write this frame to the output pipe
fwrite(jpeg_img.data(), 1, jpeg_img.size(), pipeout);
}
// Flush and close input and output pipes
fflush(pipeout);
#ifdef _MSC_VER
_pclose(pipeout);
#else
pclose(pipeout);
#endif
//It looks like we need to wait one more second at the end.
Sleep(1000);
return 0;
}

Related

FFMPEG Pipeline

I created an FFMPEG pipeline so that I can stream video frames to an RTSP server. I created a synthetic video for testing where each frame is a green number on a black background. The video plays on my screen but it does not stream to the server because I get the error "Unable to find a suitable output format for 'rtsp://10.0.0.6:8554/mystream"
My code is below. The source code is taken from the answer: How to stream frames from OpenCV C++ code to Video4Linux or ffmpeg?
int main() {
int width = 720;
int height = 1280;
int fps = 30;
FILE* pipeout = _popen("ffmpeg -f rawvideo -r 30 -video_size 720x1280 -pixel_format bgr24 -i pipe: -vcodec libx264 -crf 24 -pix_fmt yuv420p rtsp://10.0.0.6:8554/mystream", "w");
for (int i = 0; i < 100; i++)
{
Mat frame = Mat(height, width, CV_8UC3);
frame = Scalar(60, 60, 60); //Fill background with dark gray
putText(frame, to_string(i + 1), Point(width / 2 - 50 * (int)(to_string(i + 1).length()), height / 2 + 50), FONT_HERSHEY_DUPLEX, 5, Scalar(30, 255, 30), 10); // Draw a green number
imshow("frame", frame);
waitKey(1);
fwrite(frame.data, 1, width * height * 3, pipeout);
}
// Flush and close input and output pipes
fflush(pipeout);
_pclose(pipeout); //Windows
return 0;
}
When I change -f rawvideo to -f rtsp in the FFMPEG command, I no longer get the error but the program just displays the first frame on the screen and seems to get stuck. Is there a wrong parameter in the pipeline. When I change the RTSP url to a file name such as output.mkv, it works perfectly and saves the video to the file.

streaming a bitmap while it is modified in C++

What I am trying to do is use some process to alter the contents of a bitmap image ( dimensions and everything else is kept constant). While I run a process to perform transformations on it, I want to concurrently stream the image to my screen as it happens. So it's basically video without a series of images, but rather the same image in different stages of a transformation.
I am trying to do this because the bitmap I want to do this with is large, so actually saving each state as its own image and then combining will not be possible due to memory constraints.
My question is regarding the "live image display" while another process modifies the image, is there any way something like this can be done with a bitmap image?
You could stream the images in a video encoder directly.
For example you can feed ffmpeg with raw PGM/PPM images as input and you will get the compressed video as output without the need to ever create the actual images on disk.
Writing a PGM or PPM image means just generating a few header bytes followed by actual pixel values and thus is trivial to do with any language without the need of any image library. For example:
#include <stdio.h>
#include <math.h>
#include <algorithm>
int main(int argc, const char *argv[]) {
int w = 1920, h = 1080;
for (int frame=0; frame<100; frame++) {
// output frame header
printf("P5\n%i %i 255\n", w, h);
for (int y=0; y<h; y++) {
for (int x=0; x<w; x++) {
double dx = (x - w/2),
dy = (y - h/2),
d = sqrt(dx*dx + dy*dy),
a = atan2(dy, dx) - frame*2*3.14159265359/100,
value = 127 + 127*sin(a+d/10);
// output pixel
putchar(std::max(0, std::min(255, int(value))));
}
}
}
return 0;
}
generates on standard output a sequence of images that can be combined in a video directly. Running the program with
./video | ffmpeg -i - -r 60 -format pgm -y out.mp4
will generate a video named out.mp4. The video will be at 60fps (what -r is for) created from source images in PGM format (-format pgm) from standard input (option -i -) overwriting the output file if it's already present (-y).
This code was tested on linux; for this approach to work on windows you need also to set stdout to binary more with _setmode(fileno(stdout), _O_BINARY); or something similar (I didn't test on windows).
A more sophisticated way to do basically the same would be to start a child process instead of using piping thus leaving standard output usable by the program.
If you're just trying to see the results real-time, you can use something similar to page flipping / double buffering.
Basically: create two images. Image 1 is a "drawing buffer" while Image 2 is a "display buffer." Perform some portion of your drawing to image 1. Then, copy its complete contents to image 2. Continue drawing to image 1, copy, draw, copy, draw... etc. Insert a delay before each copy.

Pipe opencv frames to ffmpeg

I am trying to pipe opencv frames to ffmpeg using rawvideo format, which should accept the input as BGRBGRBGR... encoding the frame before piping is not an option.
cv::Mat frame;
cv::VideoCapture cap("cap.avi");
while(1)
{
cap >> frame;
if(!frame.data) break;
// some processing
cv::Mat array = frame.reshape(0, 1); // to make continuous
std::string output((char*) array.data, array.total() * array.elemSize());
std::cout << output;
}
with command
cap.exe | ffplay -f rawvideo -pixel_format bgr24 -video_size 1920x1080 -framerate 10 -i -
gives this kind of distorted result
I think problem is not related to ffmpeg/ffplay, but something is wrong with my frame to raw conversion
how to convert mat with 3 channels to rawvideo bgr24 format ?

ffmpeg sws_scale YUV420p to RGBA not giving correct scaling result (c++)

I am trying to scale a decoded YUV420p frame(1018x700) via sws_scale to RGBA, I am saving data to a raw video file and then playing the raw video using ffplay to see the result.
Here is my code:
sws_ctx = sws_getContext(video_dec_ctx->width, video_dec_ctx->height,AV_PIX_FMT_YUV420P, video_dec_ctx->width, video_dec_ctx->height, AV_PIX_FMT_BGR32, SWS_LANCZOS | SWS_ACCURATE_RND, 0, 0, 0);
ret = avcodec_decode_video2(video_dec_ctx, yuvframe, got_frame, &pkt);
if (ret < 0) {
std::cout<<"Error in decoding"<<std::endl;
return ret;
}else{
//the source and destination heights and widths are the same
int sourceX = video_dec_ctx->width;
int sourceY = video_dec_ctx->height;
int destX = video_dec_ctx->width;
int destY = video_dec_ctx->height;
//declare destination frame
AVFrame avFrameRGB;
avFrameRGB.linesize[0] = destX * 4;
avFrameRGB.data[0] = (uint8_t*)malloc(avFrameRGB.linesize[0] * destY);
//scale the frame to avFrameRGB
sws_scale(sws_ctx, yuvframe->data, yuvframe->linesize, 0, yuvframe->height, avFrameRGB.data, avFrameRGB.linesize);
//write to file
fwrite(avFrameRGB.data[0], 1, video_dst_bufsize, video_dst_file);
}
Here is the result without scaling (i.e. in YUV420p Format)
Here is the after scaling while playing using ffplay (i.e. in RGBA format)
I run the ffplay using the following command ('video' is the raw video file)
ffplay -f rawvideo -pix_fmt bgr32 -video_size 1018x700 video
What should I fix to make the correct scaling happen to RGB32?
I found the solution, the problem here was that I was not using the correct buffer size to write to the file.
fwrite(avFrameRGB.data[0], 1, video_dst_bufsize, video_dst_file);
The variable video_dst_file was being taken from the return value of
video_dst_bufsize = av_image_alloc(yuvframe.data, yuvframe.linesize, destX, destY, AV_PIX_FMT_YUV420P, 1);
The solution is to get the return value from and use this in the fwrite statement:
video_dst_bufsize_RGB = av_image_alloc(avFrameRGB.data, avFrameRGB.linesize, destX, destY, AV_PIX_FMT_BGR32, 1);
fwrite(avFrameRGB.data[0], 1, video_dst_bufsize_RGB, video_dst_file);

FFMPEG with x264 encoding

I'm trying ton encode video from set of jpeg images to h264, using ffmpeg + x264 for it. I init AVCodecContext in such way:
_outputCodec = avcodec_find_encoder(AV_CODEC_ID_H264);
_outputCodecContext = avcodec_alloc_context3(_outputCodec);
avcodec_get_context_defaults3(_outputCodecContext, _outputCodec);
_outputCodecContext->width = _currentWidth;
_outputCodecContext->height = _currentHeight;
_outputCodecContext->pix_fmt = AV_PIX_FMT_YUV420P;
_outputCodecContext->time_base.num = 1;
_outputCodecContext->time_base.den = 25;
_outputCodecContext->profile =FF_PROFILE_H264_BASELINE;
_outputCodecContext->level = 50;
avcodec_open return no errors, anything is OK, but when I call avcodec_encode_video2() I get such messages (I think it's from x264):
using mv_range_thread = %d
%s
profile %s, level %s
And then app crashs. My be there are more neccessary settings for codec context, when use x264 &&
Without a full version of your code it is hard to see what the actual problem is.
Firstly here is a working example of the FFMPEG library encoding RGB frames to a H264 video:
http://www.imc-store.com.au/Articles.asp?ID=276
You could expand on this example by using CImage to load in your JPGs and pass the RGB data to the FFMPEG Class to encode to a H264 video.
A few thoughts on your example though:
Have you called register all like below?
avcodec_register_all();
av_register_all();
Also I'd re-write your code to be something like below:
AVStream *st;
m_video_codec = avcodec_find_encoder(AV_CODEC_ID_H264);
st = avformat_new_stream(_outputCodec, m_video_codec);
_outputCodecContext = st->codec;
_outputCodecContext->codec_id = m_fmt->video_codec;
_outputCodecContext->bit_rate = m_AVIMOV_BPS; //Bits Per Second
_outputCodecContext->width = m_AVIMOV_WIDTH; //Note Resolution must be a multiple of 2!!
_outputCodecContext->height = m_AVIMOV_HEIGHT; //Note Resolution must be a multiple of 2!!
_outputCodecContext->time_base.den = m_AVIMOV_FPS; //Frames per second
_outputCodecContext->time_base.num = 1;
_outputCodecContext->gop_size = m_AVIMOV_GOB; // Intra frames per x P frames
_outputCodecContext->pix_fmt = AV_PIX_FMT_YUV420P;//Do not change this, H264 needs YUV format not RGB
And then you need to convert the RGB JPG picture to the YUV format using swscale as pogorskiy said.
Have a look at the linked example, I tested it on VC++2010 and it works perfectly and you can send it an RGB char array.