[Libav-user] Decoding a network video stream
Sergiu Giurgiu
sergiu.giurgiu at motiongestures.com
Tue Aug 13 21:58:28 EEST 2019
Hi,
I am using libav in a C++ application that acts as a web server. It (the
app) provides a websocket endpoint to which a webm video is being
streamed by the client, which is a normal browser that accesses the
client's webcam. If I save the received data into a file, it can be
successfully played by a media player.
What I want to do with the data, however, is to extract/decode the video
into frames, in a std::vector<cv::Mat> which then will be sent for
further processing by some other library. What I was expecting to happen
was for the frames that make up the video to be extracted on the fly, as
I receive the data.
So, what I have made was a class MediaDecoder, that has one method:
std::vector<cv::Mat> decode(const char* data, size_t length);
Essentially, send data to it and return whatever frames the decoder may
have been able to pick up.
The custom IO buffer is:
struct Buffer
{
std::vector<uint8_t> data;
int currentPos = 0;
};
In the decode method:
std::vector<cv::Mat> frames;
//add data to the buffer
for(size_t i=0;i<length;i++) {
buf.data.push_back(data[i]);
}
//do not invoke the decoders until we have 1MB of data
if(((buf.data.size() - buf.currentPos) < 1*1024*1024) &&
!initializedCodecs) return frames;
And then, if I'm invoked for the first time, I setup the entire chain:
custom IO, format context, etc.:
if(!initializedCodecs) //initialize ffmpeg objects. Custom I/O,
format, decoder, etc.
{
avioCtxPtr = std::unique_ptr<AVIOContext,avio_context_deleter>(
avio_alloc_context((uint8_t*)av_malloc(4096),4096,0,&buf,&readAVBuffer,nullptr,&seekAVBuffer),
avio_context_deleter());
if(!avioCtxPtr)
{
std::cerr << "Could not create IO buffer" << std::endl;
return frames;
}
fmt_ctx =
std::unique_ptr<AVFormatContext,avformat_context_deleter>(avformat_alloc_context(),
avformat_context_deleter());
fmt_ctx->pb = avioCtxPtr.get();
fmt_ctx->flags |=
AVFMT_FLAG_CUSTOM_IO|AVFMT_FLAG_NOBUFFER|AVFMT_FLAG_GENPTS ;
fmt_ctx->max_analyze_duration = 2 * AV_TIME_BASE; // read 2
seconds of data
{
AVFormatContext *fmtCtxRaw = fmt_ctx.get();
if (avformat_open_input(&fmtCtxRaw, "", nullptr, nullptr) <
0) {
std::cerr << "Could not open movie" << std::endl;
return frames;
}
}
if (avformat_find_stream_info(fmt_ctx.get(), nullptr) < 0) {
std::cerr << "Could not find stream information" << std::endl;
return frames;
}
if((video_stream_idx = av_find_best_stream(fmt_ctx.get(),
AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0)) < 0)
{
std::cerr << "Could not find video stream" << std::endl;
return frames;
}
AVStream *video_stream = fmt_ctx->streams[video_stream_idx];
AVCodec *dec =
avcodec_find_decoder(video_stream->codecpar->codec_id);
video_dec_ctx =
std::unique_ptr<AVCodecContext,avcodec_context_deleter>
(avcodec_alloc_context3(dec),
avcodec_context_deleter());
if (!video_dec_ctx)
{
std::cerr << "Failed to allocate the video codec context"
<< std::endl;
return frames;
}
avcodec_parameters_to_context(video_dec_ctx.get(),video_stream->codecpar);
video_dec_ctx->thread_count = 1;
/* video_dec_ctx->max_b_frames = 0;
video_dec_ctx->frame_skip_threshold = 10;*/
AVDictionary *opts = nullptr;
av_dict_set(&opts, "refcounted_frames", "1", 0);
av_dict_set(&opts, "deadline", "1", 0);
av_dict_set(&opts, "auto-alt-ref", "0", 0);
av_dict_set(&opts, "lag-in-frames", "1", 0);
av_dict_set(&opts, "rc_lookahead", "1", 0);
av_dict_set(&opts, "drop_frame", "1", 0);
av_dict_set(&opts, "error-resilient", "1", 0);
int width = video_dec_ctx->width;
videoHeight = video_dec_ctx->height;
if(avcodec_open2(video_dec_ctx.get(), dec, &opts) < 0)
{
std::cerr << "Failed to open the video codec context" <<
std::endl;
return frames;
}
AVPixelFormat pFormat = AV_PIX_FMT_BGR24;
img_convert_ctx =
std::unique_ptr<SwsContext,swscontext_deleter>(sws_getContext(width,
videoHeight,
video_dec_ctx->pix_fmt, width, videoHeight, pFormat,
SWS_BICUBIC, nullptr,
nullptr,nullptr),swscontext_deleter());
frame =
std::unique_ptr<AVFrame,avframe_deleter>(av_frame_alloc(),avframe_deleter());
frameRGB =
std::unique_ptr<AVFrame,avframe_deleter>(av_frame_alloc(),avframe_deleter());
int numBytes = av_image_get_buffer_size(pFormat, width,
videoHeight,32
/*https://stackoverflow.com/questions/35678041/what-is-linesize-alignment-meaning*/);
std::unique_ptr<uint8_t,avbuffer_deleter> imageBuffer((uint8_t
*) av_malloc(numBytes*sizeof(uint8_t)),avbuffer_deleter());
av_image_fill_arrays(frameRGB->data,frameRGB->linesize,imageBuffer.get(),pFormat,width,videoHeight,32);
frameRGB->width = width;
frameRGB->height = videoHeight;
initializedCodecs = true;
}
after that is done, upon every call I have the reading loop:
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = nullptr;
pkt.size = 0;
int read_frame_return = 0;
while ( (read_frame_return=av_read_frame(fmt_ctx.get(), &pkt)) >= 0)
{
readFrame(&frames,&pkt,video_dec_ctx.get(),frame.get(),img_convert_ctx.get(),
videoHeight,frameRGB.get());
}
avioCtxPtr->eof_reached = 0;
avioCtxPtr->error = 0;
The custom IO readAVBuffer method looks like this:
int MediaDecoder::readAVBuffer(void* opaque, uint8_t* buf, int buf_size)
{
MediaDecoder::Buffer* mbuf = (MediaDecoder::Buffer*)opaque;
int count = 0;
for(int i=0;i<buf_size;i++)
{
int index = i + mbuf->currentPos;
if(index >= (int)mbuf->data.size())
{
break;
}
count++;
buf[i] = mbuf->data.at(index);
}
if(count > 0) mbuf->currentPos+=count;
std::cout << "read : "<<count<<" "<<mbuf->currentPos<<", buff
size:"<<mbuf->data.size() << std::endl;
if(count <= 0) return AVERROR(EAGAIN);
return count;
}
As it is I get the following output:
read : 4096 , buff size:1274923 //we read 4K
[matroska,webm @ 0x63ccef0] Found unknown-length element with ID
0x18538067 at pos. 0x104 for which no syntax for parsing is available.
[matroska,webm @ 0x63ccef0] Duplicate element
[matroska,webm @ 0x63ccef0] Duplicate element
//uh oh, apparently the decoder is not happy
//then we go on and read a bunch from our buffer, until we're starting
to get these errors (lots of them):
[vp8 @ 0x63d0590] Discarding interframe without a prior keyframe!
Error sending packet for decoding -1094995529
[vp8 @ 0x63d0590] Discarding interframe without a prior keyframe!
Error sending packet for decoding -1094995529
Then we go on and we finish reading from the buffer completely, until
the remaining size is zero:
[matroska,webm @ 0x63ccef0] Read error at pos. 1274923 (0x13742b)
Now, here, various things could happen, one of them being that any
further calls to av_read_frame will no longer read from the buffer. It's
considered EOF. Other times, it's simply just a segfault with "corrupted
size vs. prev_size" message. Never been able to get that crash while
running in the debugger.
I am using libav in some very very wrong way, but I am completely
clueless on what would be the correct way to use it. Any help is
appreciated.
Thank you,
Sergiu.
More information about the Libav-user
mailing list