Experimental SkVideoDecoder, to wrap calls to ffmpeg
If you want to build ffmpeg locally, here is how I configured it: ./configure --disable-all --enable-avcodec --enable-avformat --enable-decoder=h264 --enable-parser=h264 --enable-demuxer=mov --enable-static --enable-protocol=file Bug: skia: 9085 Change-Id: If1892b62314af26e56ecb0293850f7554106c3d0 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/213664 Commit-Queue: Mike Reed <reed@google.com> Reviewed-by: Nathaniel Nifong <nifong@google.com>
This commit is contained in:
parent
e5288369c8
commit
69596470d1
20
BUILD.gn
20
BUILD.gn
@ -21,6 +21,7 @@ declare_args() {
|
||||
skia_use_angle = false
|
||||
skia_use_egl = false
|
||||
skia_use_expat = true
|
||||
skia_use_ffmpeg = false
|
||||
skia_use_fontconfig = is_linux
|
||||
skia_use_fonthost_mac = is_mac
|
||||
skia_use_freetype = is_android || is_fuchsia || is_linux
|
||||
@ -1622,6 +1623,20 @@ if (skia_enable_tools) {
|
||||
]
|
||||
}
|
||||
|
||||
if (skia_use_ffmpeg) {
|
||||
test_lib("video_decoder") {
|
||||
sources = [
|
||||
"experimental/ffmpeg/SkVideoDecoder.cpp",
|
||||
"experimental/ffmpeg/SkVideoDecoder.h",
|
||||
]
|
||||
libs = [
|
||||
"avcodec",
|
||||
"avformat",
|
||||
"avutil",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
import("gn/gm.gni")
|
||||
test_lib("gm") {
|
||||
sources = gm_sources
|
||||
@ -1638,6 +1653,11 @@ if (skia_enable_tools) {
|
||||
public_deps = [
|
||||
":gpu_tool_utils",
|
||||
]
|
||||
|
||||
if (skia_use_ffmpeg) {
|
||||
deps += [ ":video_decoder" ]
|
||||
sources += [ "gm/video_decoder.cpp" ]
|
||||
}
|
||||
}
|
||||
|
||||
import("gn/tests.gni")
|
||||
|
409
experimental/ffmpeg/SkVideoDecoder.cpp
Normal file
409
experimental/ffmpeg/SkVideoDecoder.cpp
Normal file
@ -0,0 +1,409 @@
|
||||
/*
|
||||
* Copyright 2019 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "SkVideoDecoder.h"
|
||||
#include "include/core/SkColorSpace.h"
|
||||
#include "include/core/SkImage.h"
|
||||
#include "include/core/SkYUVAIndex.h"
|
||||
|
||||
static SkYUVColorSpace get_yuvspace(AVColorSpace space) {
|
||||
// this is pretty incomplete -- TODO: look to convert more AVColorSpaces
|
||||
switch (space) {
|
||||
case AVCOL_SPC_RGB: return kIdentity_SkYUVColorSpace;
|
||||
case AVCOL_SPC_BT709: return kRec709_SkYUVColorSpace;
|
||||
case AVCOL_SPC_SMPTE170M:
|
||||
case AVCOL_SPC_SMPTE240M:
|
||||
case AVCOL_SPC_BT470BG: return kRec601_SkYUVColorSpace;
|
||||
default: break;
|
||||
}
|
||||
return kRec709_SkYUVColorSpace;
|
||||
}
|
||||
|
||||
struct av_transfer_characteristics {
|
||||
// if x < beta delta * x
|
||||
// else alpha * (x^gama)
|
||||
float alpha, beta, gamma, delta;
|
||||
};
|
||||
|
||||
// Tables extracted from vf_colorspace.c
|
||||
|
||||
const av_transfer_characteristics gTransfer[AVCOL_TRC_NB] = {
|
||||
[AVCOL_TRC_BT709] = { 1.099, 0.018, 0.45, 4.5 },
|
||||
[AVCOL_TRC_GAMMA22] = { 1.0, 0.0, 1.0 / 2.2, 0.0 },
|
||||
[AVCOL_TRC_GAMMA28] = { 1.0, 0.0, 1.0 / 2.8, 0.0 },
|
||||
[AVCOL_TRC_SMPTE170M] = { 1.099, 0.018, 0.45, 4.5 },
|
||||
[AVCOL_TRC_SMPTE240M] = { 1.1115, 0.0228, 0.45, 4.0 },
|
||||
[AVCOL_TRC_IEC61966_2_1] = { 1.055, 0.0031308, 1.0 / 2.4, 12.92 },
|
||||
[AVCOL_TRC_IEC61966_2_4] = { 1.099, 0.018, 0.45, 4.5 },
|
||||
[AVCOL_TRC_BT2020_10] = { 1.099, 0.018, 0.45, 4.5 },
|
||||
[AVCOL_TRC_BT2020_12] = { 1.0993, 0.0181, 0.45, 4.5 },
|
||||
};
|
||||
|
||||
static skcms_TransferFunction compute_transfer(AVColorTransferCharacteristic t) {
|
||||
const av_transfer_characteristics* av = &gTransfer[0];
|
||||
if ((unsigned)t < AVCOL_TRC_NB) {
|
||||
av = &gTransfer[t];
|
||||
}
|
||||
|
||||
skcms_TransferFunction linear_to_encoded = {
|
||||
av->gamma, sk_float_pow(av->alpha, 1/av->gamma), 0, av->delta, av->beta, 1 - av->alpha, 0,
|
||||
};
|
||||
skcms_TransferFunction encoded_to_linear;
|
||||
bool success = skcms_TransferFunction_invert(&linear_to_encoded, &encoded_to_linear);
|
||||
SkASSERT(success);
|
||||
|
||||
return encoded_to_linear;
|
||||
}
|
||||
|
||||
enum Whitepoint {
|
||||
WP_D65,
|
||||
WP_C,
|
||||
WP_DCI,
|
||||
WP_E,
|
||||
WP_NB,
|
||||
};
|
||||
|
||||
const SkPoint gWP[WP_NB] = {
|
||||
[WP_D65] = { 0.3127f, 0.3290f },
|
||||
[WP_C] = { 0.3100f, 0.3160f },
|
||||
[WP_DCI] = { 0.3140f, 0.3510f },
|
||||
[WP_E] = { 1/3.0f, 1/3.0f },
|
||||
};
|
||||
|
||||
#define ExpandWP(index) gWP[index].fX, gWP[index].fY
|
||||
|
||||
const SkColorSpacePrimaries gPrimaries[AVCOL_PRI_NB] = {
|
||||
[AVCOL_PRI_BT709] = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f, ExpandWP(WP_D65) },
|
||||
[AVCOL_PRI_BT470M] = { 0.670f, 0.330f, 0.210f, 0.710f, 0.140f, 0.080f, ExpandWP(WP_C) },
|
||||
[AVCOL_PRI_BT470BG] = { 0.640f, 0.330f, 0.290f, 0.600f, 0.150f, 0.060f, ExpandWP(WP_D65) },
|
||||
[AVCOL_PRI_SMPTE170M] = { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f, ExpandWP(WP_D65) },
|
||||
[AVCOL_PRI_SMPTE240M] = { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f, ExpandWP(WP_D65) },
|
||||
[AVCOL_PRI_SMPTE428] = { 0.735f, 0.265f, 0.274f, 0.718f, 0.167f, 0.009f, ExpandWP(WP_E) },
|
||||
[AVCOL_PRI_SMPTE431] = { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f, ExpandWP(WP_DCI) },
|
||||
[AVCOL_PRI_SMPTE432] = { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f, ExpandWP(WP_D65) },
|
||||
[AVCOL_PRI_FILM] = { 0.681f, 0.319f, 0.243f, 0.692f, 0.145f, 0.049f, ExpandWP(WP_C) },
|
||||
[AVCOL_PRI_BT2020] = { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f, ExpandWP(WP_D65) },
|
||||
[AVCOL_PRI_JEDEC_P22] = { 0.630f, 0.340f, 0.295f, 0.605f, 0.155f, 0.077f, ExpandWP(WP_D65) },
|
||||
};
|
||||
|
||||
sk_sp<SkColorSpace> make_colorspace(AVColorPrimaries primaries,
|
||||
AVColorTransferCharacteristic transfer) {
|
||||
if (primaries == AVCOL_PRI_BT709 && transfer == AVCOL_TRC_BT709) {
|
||||
return SkColorSpace::MakeSRGB();
|
||||
}
|
||||
|
||||
const SkColorSpacePrimaries* p = &gPrimaries[0];
|
||||
if ((unsigned)primaries < (unsigned)AVCOL_PRI_NB) {
|
||||
p = &gPrimaries[primaries];
|
||||
}
|
||||
|
||||
skcms_Matrix3x3 matrix;
|
||||
p->toXYZD50(&matrix);
|
||||
return SkColorSpace::MakeRGB(compute_transfer(transfer), matrix);
|
||||
}
|
||||
|
||||
// returns true on error (and may dump the particular error message)
|
||||
static bool check_err(int err, const int silentList[] = nullptr) {
|
||||
if (err >= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (silentList) {
|
||||
for (; *silentList; ++silentList) {
|
||||
if (*silentList == err) {
|
||||
return true; // we still report the error, but we don't printf
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char errbuf[128];
|
||||
const char *errbuf_ptr = errbuf;
|
||||
|
||||
if (av_strerror(err, errbuf, sizeof(errbuf)) < 0) {
|
||||
errbuf_ptr = strerror(AVUNERROR(err));
|
||||
}
|
||||
SkDebugf("%s\n", errbuf_ptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int skstream_read_packet(void* ctx, uint8_t* dstBuffer, int dstSize) {
|
||||
SkStream* stream = (SkStream*)ctx;
|
||||
int result = (int)stream->read(dstBuffer, dstSize);
|
||||
if (result == 0) {
|
||||
result = AVERROR_EOF;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static int64_t skstream_seek_packet(void* ctx, int64_t pos, int whence) {
|
||||
SkStream* stream = (SkStream*)ctx;
|
||||
switch (whence) {
|
||||
case SEEK_SET:
|
||||
break;
|
||||
case SEEK_CUR:
|
||||
pos = (int64_t)stream->getPosition() + pos;
|
||||
break;
|
||||
case SEEK_END:
|
||||
pos = (int64_t)stream->getLength() + pos;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
return stream->seek(SkToSizeT(pos)) ? pos : -1;
|
||||
}
|
||||
|
||||
static sk_sp<SkImage> make_yuv_420(GrContext* gr, int w, int h,
|
||||
uint8_t* const data[], int const strides[],
|
||||
SkYUVColorSpace yuv_space,
|
||||
sk_sp<SkColorSpace> cs) {
|
||||
SkImageInfo info[3];
|
||||
info[0] = SkImageInfo::Make(w, h, kGray_8_SkColorType, kOpaque_SkAlphaType);
|
||||
info[1] = SkImageInfo::Make(w/2, h/2, kGray_8_SkColorType, kOpaque_SkAlphaType);
|
||||
info[2] = SkImageInfo::Make(w/2, h/2, kGray_8_SkColorType, kOpaque_SkAlphaType);
|
||||
|
||||
SkPixmap pm[4];
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
pm[i] = SkPixmap(info[i], data[i], strides[i]);
|
||||
}
|
||||
pm[3].reset(); // no alpha
|
||||
|
||||
SkYUVAIndex indices[4];
|
||||
indices[SkYUVAIndex::kY_Index] = {0, SkColorChannel::kR};
|
||||
indices[SkYUVAIndex::kU_Index] = {1, SkColorChannel::kR};
|
||||
indices[SkYUVAIndex::kV_Index] = {2, SkColorChannel::kR};
|
||||
indices[SkYUVAIndex::kA_Index] = {-1, SkColorChannel::kR};
|
||||
|
||||
return SkImage::MakeFromYUVAPixmaps(gr, yuv_space, pm, indices, {w, h},
|
||||
kTopLeft_GrSurfaceOrigin, false, false, cs);
|
||||
}
|
||||
|
||||
// Init with illegal values, so our first compare will fail, forcing us to compute
|
||||
// the skcolorspace.
|
||||
SkVideoDecoder::ConvertedColorSpace::ConvertedColorSpace()
|
||||
: fPrimaries(AVCOL_PRI_NB), fTransfer(AVCOL_TRC_NB)
|
||||
{}
|
||||
|
||||
void SkVideoDecoder::ConvertedColorSpace::update(AVColorPrimaries primaries,
|
||||
AVColorTransferCharacteristic transfer) {
|
||||
if (fPrimaries != primaries || fTransfer != transfer) {
|
||||
fPrimaries = primaries;
|
||||
fTransfer = transfer;
|
||||
fCS = make_colorspace(primaries, transfer);
|
||||
}
|
||||
}
|
||||
|
||||
double SkVideoDecoder::computeTimeStamp(const AVFrame* frame) const {
|
||||
AVRational base = fFormatCtx->streams[fStreamIndex]->time_base;
|
||||
return 1.0 * frame->pts * base.num / base.den;
|
||||
}
|
||||
|
||||
sk_sp<SkImage> SkVideoDecoder::convertFrame(const AVFrame* frame) {
|
||||
auto yuv_space = get_yuvspace(frame->colorspace);
|
||||
|
||||
// we have a 1-entry cache for converting colorspaces
|
||||
fCSCache.update(frame->color_primaries, frame->color_trc);
|
||||
|
||||
// Are these always true? If so, we don't need to check our "cache" on each frame...
|
||||
SkASSERT(fDecoderCtx->colorspace == frame->colorspace);
|
||||
SkASSERT(fDecoderCtx->color_primaries == frame->color_primaries);
|
||||
SkASSERT(fDecoderCtx->color_trc == frame->color_trc);
|
||||
|
||||
// Is this always true? If so, we might take advantage of it, knowing up-front if we support
|
||||
// the format for the whole stream, in which case we might have to ask ffmpeg to convert it
|
||||
// to something more reasonable (for us)...
|
||||
SkASSERT(fDecoderCtx->pix_fmt == frame->format);
|
||||
|
||||
switch (frame->format) {
|
||||
case AV_PIX_FMT_YUV420P:
|
||||
return make_yuv_420(fGr, frame->width, frame->height, frame->data, frame->linesize,
|
||||
yuv_space, fCSCache.fCS);
|
||||
break;
|
||||
default:
|
||||
SkDebugf("unsupported format (for now)\n");
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sk_sp<SkImage> SkVideoDecoder::nextImage(double* timeStamp) {
|
||||
double dummyTimeStampStorage = 0;
|
||||
if (!timeStamp) {
|
||||
timeStamp = &dummyTimeStampStorage;
|
||||
}
|
||||
|
||||
if (fFormatCtx == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (fMode == kProcessing_Mode) {
|
||||
// We sit in a loop, waiting for the codec to have received enough data (packets)
|
||||
// to have at least one frame available.
|
||||
// Treat non-zero return as EOF (or error, which we will decide is also EOF)
|
||||
while (!av_read_frame(fFormatCtx, &fPacket)) {
|
||||
if (fPacket.stream_index != fStreamIndex) {
|
||||
// got a packet for a stream other than our (video) stream, so continue
|
||||
continue;
|
||||
}
|
||||
|
||||
int ret = avcodec_send_packet(fDecoderCtx, &fPacket);
|
||||
if (ret == AVERROR(EAGAIN)) {
|
||||
// may signal that we have plenty already, encouraging us to call receive_frame
|
||||
// so we don't treat this as an error.
|
||||
ret = 0;
|
||||
}
|
||||
(void)check_err(ret); // we try to continue if there was an error
|
||||
|
||||
int silentList[] = {
|
||||
-35, // Resource temporarily unavailable (need more packets)
|
||||
0,
|
||||
};
|
||||
if (check_err(avcodec_receive_frame(fDecoderCtx, fFrame), silentList)) {
|
||||
// this may be just "needs more input", so we try to continue
|
||||
} else {
|
||||
*timeStamp = this->computeTimeStamp(fFrame);
|
||||
return this->convertFrame(fFrame);
|
||||
}
|
||||
}
|
||||
|
||||
fMode = kDraining_Mode;
|
||||
(void)avcodec_send_packet(fDecoderCtx, nullptr); // signal to start draining
|
||||
}
|
||||
if (fMode == kDraining_Mode) {
|
||||
if (avcodec_receive_frame(fDecoderCtx, fFrame) >= 0) {
|
||||
*timeStamp = this->computeTimeStamp(fFrame);
|
||||
return this->convertFrame(fFrame);
|
||||
}
|
||||
// else we decide we're done
|
||||
fMode = kDone_Mode;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SkVideoDecoder::SkVideoDecoder(GrContext* gr) : fGr(gr) {}
|
||||
|
||||
SkVideoDecoder::~SkVideoDecoder() {
|
||||
this->reset();
|
||||
}
|
||||
|
||||
void SkVideoDecoder::reset() {
|
||||
if (fFrame) {
|
||||
av_frame_free(&fFrame);
|
||||
fFrame = nullptr;
|
||||
}
|
||||
if (fDecoderCtx) {
|
||||
avcodec_free_context(&fDecoderCtx);
|
||||
fDecoderCtx = nullptr;
|
||||
}
|
||||
if (fFormatCtx) {
|
||||
avformat_close_input(&fFormatCtx);
|
||||
fFormatCtx = nullptr;
|
||||
}
|
||||
if (fStreamCtx) {
|
||||
av_freep(&fStreamCtx->buffer);
|
||||
avio_context_free(&fStreamCtx);
|
||||
fStreamCtx = nullptr;
|
||||
}
|
||||
|
||||
fStream.reset(nullptr);
|
||||
fStreamIndex = -1;
|
||||
fMode = kDone_Mode;
|
||||
}
|
||||
|
||||
bool SkVideoDecoder::loadStream(std::unique_ptr<SkStream> stream) {
|
||||
this->reset();
|
||||
if (!stream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int bufferSize = 4 * 1024;
|
||||
uint8_t* buffer = (uint8_t*)av_malloc(bufferSize);
|
||||
if (!buffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fStream = std::move(stream);
|
||||
fStreamCtx = avio_alloc_context(buffer, bufferSize, 0, fStream.get(),
|
||||
skstream_read_packet, nullptr, skstream_seek_packet);
|
||||
if (!fStreamCtx) {
|
||||
av_freep(buffer);
|
||||
this->reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
fFormatCtx = avformat_alloc_context();
|
||||
if (!fFormatCtx) {
|
||||
this->reset();
|
||||
return false;
|
||||
}
|
||||
fFormatCtx->pb = fStreamCtx;
|
||||
|
||||
int err = avformat_open_input(&fFormatCtx, nullptr, nullptr, nullptr);
|
||||
if (err < 0) {
|
||||
SkDebugf("avformat_open_input failed %d\n", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
AVCodec* codec;
|
||||
fStreamIndex = av_find_best_stream(fFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
|
||||
if (fStreamIndex < 0) {
|
||||
SkDebugf("av_find_best_stream failed %d\n", fStreamIndex);
|
||||
this->reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
SkASSERT(codec);
|
||||
fDecoderCtx = avcodec_alloc_context3(codec);
|
||||
|
||||
AVStream* strm = fFormatCtx->streams[fStreamIndex];
|
||||
if ((err = avcodec_parameters_to_context(fDecoderCtx, strm->codecpar)) < 0) {
|
||||
SkDebugf("avcodec_parameters_to_context failed %d\n", err);
|
||||
this->reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((err = avcodec_open2(fDecoderCtx, codec, nullptr)) < 0) {
|
||||
SkDebugf("avcodec_open2 failed %d\n", err);
|
||||
this->reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
fFrame = av_frame_alloc();
|
||||
SkASSERT(fFrame);
|
||||
|
||||
av_init_packet(&fPacket); // is there a "free" call?
|
||||
|
||||
fMode = kProcessing_Mode;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SkISize SkVideoDecoder::dimensions() const {
|
||||
if (!fFormatCtx) {
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
AVStream* strm = fFormatCtx->streams[fStreamIndex];
|
||||
return {strm->codecpar->width, strm->codecpar->height};
|
||||
}
|
||||
|
||||
double SkVideoDecoder::duration() const {
|
||||
if (!fFormatCtx) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
AVStream* strm = fFormatCtx->streams[fStreamIndex];
|
||||
AVRational base = strm->time_base;
|
||||
return 1.0 * strm->duration * base.num / base.den;
|
||||
}
|
||||
|
||||
bool SkVideoDecoder::rewind() {
|
||||
auto stream = std::move(fStream);
|
||||
this->reset();
|
||||
if (stream) {
|
||||
stream->rewind();
|
||||
}
|
||||
return this->loadStream(std::move(stream));
|
||||
}
|
76
experimental/ffmpeg/SkVideoDecoder.h
Normal file
76
experimental/ffmpeg/SkVideoDecoder.h
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2019 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef SkVideDecoder_DEFINED
|
||||
#define SkVideDecoder_DEFINED
|
||||
|
||||
#include "include/core/SkImage.h"
|
||||
|
||||
extern "C" {
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavformat/avio.h"
|
||||
#include "libavutil/pixdesc.h"
|
||||
}
|
||||
|
||||
class SkVideoDecoder {
|
||||
public:
|
||||
SkVideoDecoder(GrContext* gr = nullptr);
|
||||
~SkVideoDecoder();
|
||||
|
||||
void reset();
|
||||
void setGrContext(GrContext* gr) { fGr = gr; }
|
||||
|
||||
bool loadStream(std::unique_ptr<SkStream>);
|
||||
bool rewind();
|
||||
|
||||
SkISize dimensions() const;
|
||||
double duration() const; // in seconds
|
||||
|
||||
// Returns each image in the video, or nullptr on eof
|
||||
sk_sp<SkImage> nextImage(double* timeStamp = nullptr);
|
||||
|
||||
private:
|
||||
sk_sp<SkImage> convertFrame(const AVFrame*);
|
||||
double computeTimeStamp(const AVFrame*) const;
|
||||
|
||||
struct ConvertedColorSpace {
|
||||
AVColorPrimaries fPrimaries;
|
||||
AVColorTransferCharacteristic fTransfer;
|
||||
// fCS is the converted skia form of the above enums
|
||||
sk_sp<SkColorSpace> fCS;
|
||||
|
||||
// Init with illegal values, so our first compare will fail, forcing us to compute
|
||||
// the skcolorspace.
|
||||
ConvertedColorSpace();
|
||||
|
||||
void update(AVColorPrimaries, AVColorTransferCharacteristic);
|
||||
};
|
||||
|
||||
GrContext* fGr = nullptr; // not owned by us
|
||||
|
||||
std::unique_ptr<SkStream> fStream;
|
||||
|
||||
AVIOContext* fStreamCtx = nullptr;
|
||||
AVFormatContext* fFormatCtx = nullptr;
|
||||
AVCodecContext* fDecoderCtx = nullptr;
|
||||
int fStreamIndex = -1; // fFormatCtx->stream[...]
|
||||
|
||||
AVPacket fPacket;
|
||||
AVFrame* fFrame = nullptr;
|
||||
ConvertedColorSpace fCSCache;
|
||||
|
||||
enum Mode {
|
||||
kProcessing_Mode,
|
||||
kDraining_Mode,
|
||||
kDone_Mode,
|
||||
};
|
||||
Mode fMode = kDone_Mode;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
66
gm/video_decoder.cpp
Normal file
66
gm/video_decoder.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2019 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "experimental/ffmpeg/SkVideoDecoder.h"
|
||||
#include "gm/gm.h"
|
||||
#include "include/core/SkCanvas.h"
|
||||
#include "include/core/SkStream.h"
|
||||
|
||||
class VideoDecoderGM : public skiagm::GM {
|
||||
SkVideoDecoder fDecoder;
|
||||
|
||||
public:
|
||||
VideoDecoderGM() {}
|
||||
|
||||
protected:
|
||||
|
||||
SkString onShortName() override {
|
||||
return SkString("videodecoder");
|
||||
}
|
||||
|
||||
SkISize onISize() override {
|
||||
return SkISize::Make(1024, 768);
|
||||
}
|
||||
|
||||
void onOnceBeforeDraw() override {
|
||||
if (!fDecoder.loadStream(SkStream::MakeFromFile("/skia/ice.mp4"))) {
|
||||
SkDebugf("could not load movie file\n");
|
||||
}
|
||||
SkDebugf("duration %g\n", fDecoder.duration());
|
||||
}
|
||||
|
||||
void onDraw(SkCanvas* canvas) override {
|
||||
GrContext* gr = canvas->getGrContext();
|
||||
if (!gr) {
|
||||
return;
|
||||
}
|
||||
|
||||
fDecoder.setGrContext(gr); // gr can change over time in viewer
|
||||
|
||||
double timeStamp;
|
||||
auto img = fDecoder.nextImage(&timeStamp);
|
||||
if (!img) {
|
||||
(void)fDecoder.rewind();
|
||||
img = fDecoder.nextImage(&timeStamp);
|
||||
}
|
||||
if (img) {
|
||||
if (0) {
|
||||
SkDebugf("ts %g\n", timeStamp);
|
||||
}
|
||||
canvas->drawImage(img, 10, 10, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool onAnimate(const AnimTimer& timer) override {
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef GM INHERITED;
|
||||
};
|
||||
DEF_GM( return new VideoDecoderGM; )
|
||||
|
Loading…
Reference in New Issue
Block a user