|
|
@ -3,6 +3,9 @@ |
|
|
|
#include <assert.h> |
|
|
|
#include <assert.h> |
|
|
|
#include <unistd.h> |
|
|
|
#include <unistd.h> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <QDebug> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int ffmpeg_lockmgr_cb(void **arg, enum AVLockOp op) { |
|
|
|
static int ffmpeg_lockmgr_cb(void **arg, enum AVLockOp op) { |
|
|
|
std::mutex *mutex = (std::mutex *)*arg; |
|
|
|
std::mutex *mutex = (std::mutex *)*arg; |
|
|
|
switch (op) { |
|
|
|
switch (op) { |
|
|
@ -21,122 +24,173 @@ static int ffmpeg_lockmgr_cb(void **arg, enum AVLockOp op) { |
|
|
|
return 0; |
|
|
|
return 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
FrameReader::FrameReader(const std::string &fn) : url(fn) { |
|
|
|
class AVInitializer { |
|
|
|
|
|
|
|
public: |
|
|
|
|
|
|
|
AVInitializer() { |
|
|
|
int ret = av_lockmgr_register(ffmpeg_lockmgr_cb); |
|
|
|
int ret = av_lockmgr_register(ffmpeg_lockmgr_cb); |
|
|
|
assert(ret >= 0); |
|
|
|
assert(ret >= 0); |
|
|
|
|
|
|
|
|
|
|
|
avformat_network_init(); |
|
|
|
|
|
|
|
av_register_all(); |
|
|
|
av_register_all(); |
|
|
|
|
|
|
|
avformat_network_init(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
~AVInitializer() { avformat_network_deinit(); } |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static AVInitializer av_initializer; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FrameReader::FrameReader(const std::string &url, QObject *parent) : url_(url), QObject(parent) { |
|
|
|
|
|
|
|
process_thread_ = QThread::create(&FrameReader::process, this); |
|
|
|
|
|
|
|
connect(process_thread_, &QThread::finished, process_thread_, &QThread::deleteLater); |
|
|
|
|
|
|
|
process_thread_->start(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
FrameReader::~FrameReader() { |
|
|
|
FrameReader::~FrameReader() { |
|
|
|
|
|
|
|
// wait until thread is finished.
|
|
|
|
exit_ = true; |
|
|
|
exit_ = true; |
|
|
|
thread.join(); |
|
|
|
process_thread_->wait(); |
|
|
|
for (auto &f : frames) { |
|
|
|
cv_decode_.notify_all(); |
|
|
|
delete f->pkt; |
|
|
|
cv_frame_.notify_all(); |
|
|
|
if (f->picture) { |
|
|
|
if (decode_thread_.joinable()) { |
|
|
|
av_frame_free(&f->picture); |
|
|
|
decode_thread_.join(); |
|
|
|
} |
|
|
|
} |
|
|
|
delete f; |
|
|
|
|
|
|
|
} |
|
|
|
// free all.
|
|
|
|
avcodec_free_context(&pCodecCtx); |
|
|
|
for (auto &f : frames_) { |
|
|
|
avformat_free_context(pFormatCtx); |
|
|
|
av_free_packet(&f.pkt); |
|
|
|
sws_freeContext(sws_ctx); |
|
|
|
if (f.data) { |
|
|
|
avformat_network_deinit(); |
|
|
|
delete[] f.data; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
while (!buffer_pool.empty()) { |
|
|
|
|
|
|
|
delete[] buffer_pool.front(); |
|
|
|
|
|
|
|
buffer_pool.pop(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
av_frame_free(&frmRgb_); |
|
|
|
|
|
|
|
avcodec_close(pCodecCtx_); |
|
|
|
|
|
|
|
avcodec_free_context(&pCodecCtx_); |
|
|
|
|
|
|
|
avformat_close_input(&pFormatCtx_); |
|
|
|
|
|
|
|
sws_freeContext(sws_ctx_); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void FrameReader::process() { |
|
|
|
void FrameReader::process() { |
|
|
|
if (avformat_open_input(&pFormatCtx, url.c_str(), NULL, NULL) != 0) { |
|
|
|
if (processFrames()) { |
|
|
|
fprintf(stderr, "error loading %s\n", url.c_str()); |
|
|
|
decode_thread_ = std::thread(&FrameReader::decodeThread, this); |
|
|
|
valid = false; |
|
|
|
} |
|
|
|
return; |
|
|
|
if (!exit_) { |
|
|
|
|
|
|
|
emit finished(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool FrameReader::processFrames() { |
|
|
|
|
|
|
|
if (avformat_open_input(&pFormatCtx_, url_.c_str(), NULL, NULL) != 0) { |
|
|
|
|
|
|
|
qDebug() << "error loading " << url_.c_str(); |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
avformat_find_stream_info(pFormatCtx, NULL); |
|
|
|
avformat_find_stream_info(pFormatCtx_, NULL); |
|
|
|
av_dump_format(pFormatCtx, 0, url.c_str(), 0); |
|
|
|
av_dump_format(pFormatCtx_, 0, url_.c_str(), 0); |
|
|
|
|
|
|
|
|
|
|
|
auto pCodecCtxOrig = pFormatCtx->streams[0]->codec; |
|
|
|
auto pCodecCtxOrig = pFormatCtx_->streams[0]->codec; |
|
|
|
auto pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id); |
|
|
|
auto pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id); |
|
|
|
assert(pCodec != NULL); |
|
|
|
assert(pCodec); |
|
|
|
|
|
|
|
|
|
|
|
pCodecCtx = avcodec_alloc_context3(pCodec); |
|
|
|
pCodecCtx_ = avcodec_alloc_context3(pCodec); |
|
|
|
int ret = avcodec_copy_context(pCodecCtx, pCodecCtxOrig); |
|
|
|
int ret = avcodec_copy_context(pCodecCtx_, pCodecCtxOrig); |
|
|
|
assert(ret == 0); |
|
|
|
assert(ret == 0); |
|
|
|
|
|
|
|
|
|
|
|
ret = avcodec_open2(pCodecCtx, pCodec, NULL); |
|
|
|
ret = avcodec_open2(pCodecCtx_, pCodec, NULL); |
|
|
|
assert(ret >= 0); |
|
|
|
assert(ret >= 0); |
|
|
|
|
|
|
|
|
|
|
|
width = pCodecCtxOrig->width; |
|
|
|
width = pCodecCtxOrig->width; |
|
|
|
height = pCodecCtxOrig->height; |
|
|
|
height = pCodecCtxOrig->height; |
|
|
|
|
|
|
|
|
|
|
|
sws_ctx = sws_getContext(width, height, AV_PIX_FMT_YUV420P, |
|
|
|
sws_ctx_ = sws_getContext(width, height, AV_PIX_FMT_YUV420P, |
|
|
|
width, height, AV_PIX_FMT_BGR24, |
|
|
|
width, height, AV_PIX_FMT_BGR24, |
|
|
|
SWS_BILINEAR, NULL, NULL, NULL); |
|
|
|
SWS_BILINEAR, NULL, NULL, NULL); |
|
|
|
assert(sws_ctx != NULL); |
|
|
|
assert(sws_ctx_); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frmRgb_ = av_frame_alloc(); |
|
|
|
|
|
|
|
assert(frmRgb_); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frames_.reserve(60 * 20); // 20fps, one minute
|
|
|
|
do { |
|
|
|
do { |
|
|
|
AVPacket *pkt = new AVPacket; |
|
|
|
Frame &frame = frames_.emplace_back(); |
|
|
|
if (av_read_frame(pFormatCtx, pkt) < 0) { |
|
|
|
if (av_read_frame(pFormatCtx_, &frame.pkt) < 0) { |
|
|
|
delete pkt; |
|
|
|
frames_.pop_back(); |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
Frame *frame = new Frame; |
|
|
|
} while (!exit_); |
|
|
|
frame->pkt = pkt; |
|
|
|
|
|
|
|
frames.push_back(frame); |
|
|
|
|
|
|
|
} while (true); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
printf("framereader download done\n"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thread = std::thread(&FrameReader::decodeThread, this); |
|
|
|
valid_ = !exit_; |
|
|
|
|
|
|
|
return valid_; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void FrameReader::decodeThread() { |
|
|
|
uint8_t *FrameReader::get(int idx) { |
|
|
|
while (!exit_) { |
|
|
|
if (!valid_ || idx < 0 || idx >= frames_.size()) { |
|
|
|
int gop = 0; |
|
|
|
return nullptr; |
|
|
|
{ |
|
|
|
|
|
|
|
std::unique_lock lk(mutex); |
|
|
|
|
|
|
|
cv_decode.wait(lk, [=] { return exit_ || decode_idx != -1; }); |
|
|
|
|
|
|
|
if (exit_) break; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gop = std::max(decode_idx - decode_idx % 15, 0); |
|
|
|
|
|
|
|
decode_idx = -1; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for (int i = gop; i < std::min(gop + 15, (int)frames.size()); ++i) { |
|
|
|
{ |
|
|
|
if (frames[i]->picture != nullptr) continue; |
|
|
|
std::unique_lock lk(mutex_); |
|
|
|
|
|
|
|
decode_idx_ = idx; |
|
|
|
|
|
|
|
cv_decode_.notify_one(); |
|
|
|
|
|
|
|
cv_frame_.wait(lk, [=] { return exit_ || frames_[idx].data || frames_[idx].failed; }); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int frameFinished; |
|
|
|
return frames_[idx].data; |
|
|
|
AVFrame *pFrame = av_frame_alloc(); |
|
|
|
} |
|
|
|
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, frames[i]->pkt); |
|
|
|
|
|
|
|
AVFrame *picture = toRGB(pFrame); |
|
|
|
|
|
|
|
av_frame_free(&pFrame); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::unique_lock lk(mutex); |
|
|
|
void FrameReader::decodeThread() { |
|
|
|
frames[i]->picture = picture; |
|
|
|
int idx = 0; |
|
|
|
cv_frame.notify_all(); |
|
|
|
while (!exit_) { |
|
|
|
|
|
|
|
const int from = std::max(idx, 0); |
|
|
|
|
|
|
|
const int to = std::min(idx + 20, (int)frames_.size()); |
|
|
|
|
|
|
|
for (int i = 0; i < frames_.size() && !exit_; ++i) { |
|
|
|
|
|
|
|
Frame &frame = frames_[i]; |
|
|
|
|
|
|
|
if (i >= from && i < to) { |
|
|
|
|
|
|
|
if (frame.data || frame.failed) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t *dat = decodeFrame(&frame.pkt); |
|
|
|
|
|
|
|
std::unique_lock lk(mutex_); |
|
|
|
|
|
|
|
frame.data = dat; |
|
|
|
|
|
|
|
frame.failed = !dat; |
|
|
|
|
|
|
|
cv_frame_.notify_all(); |
|
|
|
|
|
|
|
} else if (frame.data) { |
|
|
|
|
|
|
|
buffer_pool.push(frame.data); |
|
|
|
|
|
|
|
frame.data = nullptr; |
|
|
|
|
|
|
|
frame.failed = false; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AVFrame *FrameReader::toRGB(AVFrame *pFrame) { |
|
|
|
// sleep & wait
|
|
|
|
AVFrame *pFrameRGB = av_frame_alloc(); |
|
|
|
std::unique_lock lk(mutex_); |
|
|
|
int numBytes = avpicture_get_size(AV_PIX_FMT_BGR24, pFrame->width, pFrame->height); |
|
|
|
cv_decode_.wait(lk, [=] { return exit_ || decode_idx_ != -1; }); |
|
|
|
uint8_t *buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t)); |
|
|
|
idx = decode_idx_; |
|
|
|
avpicture_fill((AVPicture *)pFrameRGB, buffer, AV_PIX_FMT_BGR24, pFrame->width, pFrame->height); |
|
|
|
decode_idx_ = -1; |
|
|
|
sws_scale(sws_ctx, (uint8_t const *const *)pFrame->data, |
|
|
|
} |
|
|
|
pFrame->linesize, 0, pFrame->height, |
|
|
|
|
|
|
|
pFrameRGB->data, pFrameRGB->linesize); |
|
|
|
|
|
|
|
return pFrameRGB; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
uint8_t *FrameReader::get(int idx) { |
|
|
|
uint8_t *FrameReader::decodeFrame(AVPacket *pkt) { |
|
|
|
if (!valid || idx < 0 || idx >= frames.size()) return nullptr; |
|
|
|
int gotFrame; |
|
|
|
|
|
|
|
AVFrame *f = av_frame_alloc(); |
|
|
|
|
|
|
|
avcodec_decode_video2(pCodecCtx_, f, &gotFrame, pkt); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t *dat = nullptr; |
|
|
|
|
|
|
|
if (gotFrame) { |
|
|
|
|
|
|
|
if (!buffer_pool.empty()) { |
|
|
|
|
|
|
|
dat = buffer_pool.front(); |
|
|
|
|
|
|
|
buffer_pool.pop(); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
dat = new uint8_t[getRGBSize()]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
std::unique_lock lk(mutex); |
|
|
|
int ret = avpicture_fill((AVPicture *)frmRgb_, dat, AV_PIX_FMT_BGR24, f->width, f->height); |
|
|
|
decode_idx = idx; |
|
|
|
assert(ret > 0); |
|
|
|
cv_decode.notify_one(); |
|
|
|
if (sws_scale(sws_ctx_, (const uint8_t **)f->data, f->linesize, 0, |
|
|
|
Frame *frame = frames[idx]; |
|
|
|
f->height, frmRgb_->data, frmRgb_->linesize) <= 0) { |
|
|
|
if (!frame->picture) { |
|
|
|
delete[] dat; |
|
|
|
cv_frame.wait(lk, [=] { return exit_ || frame->picture != nullptr; }); |
|
|
|
dat = nullptr; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return frame->picture ? frame->picture->data[0] : nullptr; |
|
|
|
av_frame_free(&f); |
|
|
|
|
|
|
|
return dat; |
|
|
|
} |
|
|
|
} |
|
|
|