/*
* Copyright 2016 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
// primary header
#include "vaapiDecoderJPEG.h"
// library headers
#include "codecparsers/jpegParser.h"
#include "common/common_def.h"
// system headers
#include <cassert>
using ::YamiParser::JPEG::Component;
using ::YamiParser::JPEG::FrameHeader;
using ::YamiParser::JPEG::HuffTable;
using ::YamiParser::JPEG::HuffTables;
using ::YamiParser::JPEG::Parser;
using ::YamiParser::JPEG::QuantTable;
using ::YamiParser::JPEG::QuantTables;
using ::YamiParser::JPEG::ScanHeader;
using ::YamiParser::JPEG::Defaults;
using ::std::function;
using ::std::bind;
using ::std::ref;
namespace YamiMediaCodec {
#define JPEG_SURFACE_NUM 2
struct Slice {
Slice() : data(NULL), start(0) , length(0) { }
const uint8_t* data;
uint32_t start;
uint32_t length;
};
class VaapiDecoderJPEG::Impl
{
public:
typedef function<YamiStatus(void)> DecodeHandler;
Impl(const DecodeHandler& start, const DecodeHandler& finish)
: m_startHandler(start)
, m_finishHandler(finish)
, m_parser()
, m_dcHuffmanTables(Defaults::instance().dcHuffTables())
, m_acHuffmanTables(Defaults::instance().acHuffTables())
, m_quantizationTables(Defaults::instance().quantTables())
, m_slice()
, m_decodeStatus(YAMI_SUCCESS)
{
}
YamiStatus decode(const uint8_t* data, const uint32_t size)
{
using namespace ::YamiParser::JPEG;
//this mainly for codec flush, jpeg/mjpeg do not have to flush.
//just return success for this
if (!data || !size)
return YAMI_SUCCESS;
Parser::Callback defaultCallback =
bind(&Impl::onMarker, ref(*this));
Parser::Callback sofCallback =
bind(&Impl::onStartOfFrame, ref(*this));
m_slice.data = data;
m_parser.reset(new Parser(data, size));
m_parser->registerCallback(M_SOI, defaultCallback);
m_parser->registerCallback(M_EOI, defaultCallback);
m_parser->registerCallback(M_SOS, defaultCallback);
m_parser->registerCallback(M_DHT, defaultCallback);
m_parser->registerCallback(M_DQT, defaultCallback);
m_parser->registerStartOfFrameCallback(sofCallback);
if (!m_parser->parse())
m_decodeStatus = YAMI_FAIL;
return m_decodeStatus;
}
const FrameHeader::Shared& frameHeader() const
{
return m_parser->frameHeader();
}
const ScanHeader::Shared& scanHeader() const
{
return m_parser->scanHeader();
}
const unsigned restartInterval() const
{
return m_parser->restartInterval();
}
const HuffTables& dcHuffmanTables() const { return m_dcHuffmanTables; }
const HuffTables& acHuffmanTables() const { return m_acHuffmanTables; }
const QuantTables& quantTables() const { return m_quantizationTables; }
const Slice& slice() const { return m_slice; }
private:
Parser::CallbackResult onMarker()
{
using namespace ::YamiParser::JPEG;
m_decodeStatus = YAMI_SUCCESS;
switch(m_parser->current().marker) {
case M_SOI:
m_slice.start = 0;
m_slice.length = 0;
break;
case M_SOS:
m_slice.start = m_parser->current().position + 1
+ m_parser->current().length;
break;
case M_EOI:
if (m_slice.start >= m_parser->current().position) {
// we can't have zero or negative length
m_decodeStatus = YAMI_FAIL;
} else {
m_slice.length = m_parser->current().position - m_slice.start;
m_decodeStatus = m_finishHandler();
}
break;
case M_DQT:
m_quantizationTables = m_parser->quantTables();
break;
case M_DHT:
m_dcHuffmanTables = m_parser->dcHuffTables();
m_acHuffmanTables = m_parser->acHuffTables();
break;
default:
m_decodeStatus = YAMI_FAIL;
}
if (m_decodeStatus != YAMI_SUCCESS)
return Parser::ParseSuspend;
return Parser::ParseContinue;
}
Parser::CallbackResult onStartOfFrame()
{
m_decodeStatus = m_startHandler();
if (m_decodeStatus != YAMI_SUCCESS)
return Parser::ParseSuspend;
return Parser::ParseContinue;
}
const DecodeHandler m_startHandler; // called after SOF
const DecodeHandler m_finishHandler; // called after EOI
Parser::Shared m_parser;
HuffTables m_dcHuffmanTables;
HuffTables m_acHuffmanTables;
QuantTables m_quantizationTables;
Slice m_slice;
YamiStatus m_decodeStatus;
};
VaapiDecoderJPEG::VaapiDecoderJPEG()
: VaapiDecoderBase::VaapiDecoderBase()
, m_impl()
, m_picture()
{
return;
}
YamiStatus VaapiDecoderJPEG::fillPictureParam()
{
const FrameHeader::Shared frame = m_impl->frameHeader();
const size_t numComponents = frame->components.size();
if (numComponents > 4)
return YAMI_FAIL;
VAPictureParameterBufferJPEGBaseline* vaPicParam(NULL);
if (!m_picture->editPicture(vaPicParam))
return YAMI_FAIL;
for (size_t i(0); i < numComponents; ++i) {
const Component::Shared& component = frame->components[i];
vaPicParam->components[i].component_id = component->id;
vaPicParam->components[i].h_sampling_factor = component->hSampleFactor;
vaPicParam->components[i].v_sampling_factor = component->vSampleFactor;
vaPicParam->components[i].quantiser_table_selector =
component->quantTableNumber;
}
vaPicParam->picture_width = frame->imageWidth;
vaPicParam->picture_height = frame->imageHeight;
vaPicParam->num_components = frame->components.size();
return YAMI_SUCCESS;
}
YamiStatus VaapiDecoderJPEG::fillSliceParam()
{
const ScanHeader::Shared scan = m_impl->scanHeader();
const FrameHeader::Shared frame = m_impl->frameHeader();
const Slice& slice = m_impl->slice();
VASliceParameterBufferJPEGBaseline *sliceParam(NULL);
if (!m_picture->newSlice(sliceParam, slice.data + slice.start, slice.length))
return YAMI_FAIL;
for (size_t i(0); i < scan->numComponents; ++i) {
sliceParam->components[i].component_selector =
scan->components[i]->id;
sliceParam->components[i].dc_table_selector =
scan->components[i]->dcTableNumber;
sliceParam->components[i].ac_table_selector =
scan->components[i]->acTableNumber;
}
sliceParam->restart_interval = m_impl->restartInterval();
sliceParam->num_components = scan->numComponents;
sliceParam->slice_horizontal_position = 0;
sliceParam->slice_vertical_position = 0;
int width = frame->imageWidth;
int height = frame->imageHeight;
int maxHSample = frame->maxHSampleFactor << 3;
int maxVSample = frame->maxVSampleFactor << 3;
int codedWidth;
int codedHeight;
if (scan->numComponents == 1) { /* Noninterleaved Scan */
if (frame->components.front() == scan->components.front()) {
/* Y mcu */
codedWidth = width >> 3;
codedHeight = height >> 3;
} else {
/* Cr, Cb mcu */
codedWidth = width >> 4;
codedHeight = height >> 4;
}
} else { /* Interleaved Scan */
codedWidth = (width + maxHSample - 1) / maxHSample;
codedHeight = (height + maxVSample - 1) / maxVSample;
}
sliceParam->num_mcus = codedWidth * codedHeight;
return YAMI_SUCCESS;
}
YamiStatus VaapiDecoderJPEG::loadQuantizationTables()
{
using namespace ::YamiParser::JPEG;
VAIQMatrixBufferJPEGBaseline* vaIqMatrix(NULL);
if (!m_picture->editIqMatrix(vaIqMatrix))
return YAMI_FAIL;
size_t numTables = std::min(
N_ELEMENTS(vaIqMatrix->quantiser_table), size_t(NUM_QUANT_TBLS));
for (size_t i(0); i < numTables; ++i) {
const QuantTable::Shared& quantTable = m_impl->quantTables()[i];
vaIqMatrix->load_quantiser_table[i] = bool(quantTable);
if (!quantTable)
continue;
assert(quantTable->precision == 0);
for (uint32_t j(0); j < DCTSIZE2; ++j)
vaIqMatrix->quantiser_table[i][j] = quantTable->values[j];
}
return YAMI_SUCCESS;
}
YamiStatus VaapiDecoderJPEG::loadHuffmanTables()
{
using namespace ::YamiParser::JPEG;
VAHuffmanTableBufferJPEGBaseline* vaHuffmanTable(NULL);
if (!m_picture->editHufTable(vaHuffmanTable))
return YAMI_FAIL;
size_t numTables = std::min(
N_ELEMENTS(vaHuffmanTable->huffman_table), size_t(NUM_HUFF_TBLS));
for (size_t i(0); i < numTables; ++i) {
const HuffTable::Shared& dcTable = m_impl->dcHuffmanTables()[i];
const HuffTable::Shared& acTable = m_impl->acHuffmanTables()[i];
bool valid = bool(dcTable) && bool(acTable);
vaHuffmanTable->load_huffman_table[i] = valid;
if (!valid)
continue;
// Load DC Table
memcpy(vaHuffmanTable->huffman_table[i].num_dc_codes,
&dcTable->codes[0],
sizeof(vaHuffmanTable->huffman_table[i].num_dc_codes));
memcpy(vaHuffmanTable->huffman_table[i].dc_values,
&dcTable->values[0],
sizeof(vaHuffmanTable->huffman_table[i].dc_values));
// Load AC Table
memcpy(vaHuffmanTable->huffman_table[i].num_ac_codes,
&acTable->codes[0],
sizeof(vaHuffmanTable->huffman_table[i].num_ac_codes));
memcpy(vaHuffmanTable->huffman_table[i].ac_values,
&acTable->values[0],
sizeof(vaHuffmanTable->huffman_table[i].ac_values));
memset(vaHuffmanTable->huffman_table[i].pad,
0, sizeof(vaHuffmanTable->huffman_table[i].pad));
}
return YAMI_SUCCESS;
}
YamiStatus VaapiDecoderJPEG::decode(VideoDecodeBuffer* buffer)
{
if (!buffer || !buffer->data)
return YAMI_SUCCESS;
m_currentPTS = buffer->timeStamp;
if (!m_impl.get())
m_impl.reset(new VaapiDecoderJPEG::Impl(
bind(&VaapiDecoderJPEG::start, ref(*this), &m_configBuffer),
bind(&VaapiDecoderJPEG::finish, ref(*this))));
return m_impl->decode(buffer->data, buffer->size);
}
#define RETURN_FORMAT(f) \
do { \
uint32_t fourcc = f; \
DEBUG("jpeg format %.4s", (char*) & fourcc); \
return fourcc; \
} while (0)
//get frame fourcc, return 0 for unsupport format
static uint32_t getFourcc(const FrameHeader::Shared& frame)
{
if (frame->components.size() == 1) // monochrome image
RETURN_FORMAT(YAMI_FOURCC_Y800);
if (frame->components.size() != 3) {
ERROR("unsupported component size %d", (int)frame->components.size());
return 0;
}
int h1 = frame->components[0]->hSampleFactor;
int h2 = frame->components[1]->hSampleFactor;
int h3 = frame->components[2]->hSampleFactor;
int v1 = frame->components[0]->vSampleFactor;
int v2 = frame->components[1]->vSampleFactor;
int v3 = frame->components[2]->vSampleFactor;
if (h2 != h3 || v2 != v3) {
ERROR("unsupported format h1 = %d, h2 = %d, h3 = %d, v1 = %d, v2 = %d, v3 = %d", h1, h2, h3, v1, v2, v3);
return 0;
}
if (h1 == h2) {
if (v1 == v2)
RETURN_FORMAT(YAMI_FOURCC_444P);
if (v1 == 2 * v2)
RETURN_FORMAT(YAMI_FOURCC_422V);
}
else if (h1 == 2 * h2) {
if (v1 == v2)
RETURN_FORMAT(YAMI_FOURCC_422H);
if (v1 == 2 * v2)
RETURN_FORMAT(YAMI_FOURCC_IMC3);
}
ERROR("unsupported format h1 = %d, h2 = %d, h3 = %d, v1 = %d, v2 = %d, v3 = %d", h1, h2, h3, v1, v2, v3);
return 0;
}
YamiStatus VaapiDecoderJPEG::start(VideoConfigBuffer* buffer)
{
DEBUG("%s", __func__);
return YAMI_SUCCESS;
}
YamiStatus VaapiDecoderJPEG::finish()
{
if (!m_impl->frameHeader()) {
ERROR("Start of Frame (SOF) not found");
return YAMI_FAIL;
}
if (!m_impl->scanHeader()) {
ERROR("Start of Scan (SOS) not found");
return YAMI_FAIL;
}
YamiStatus status;
status = ensureContext();
if (status != YAMI_SUCCESS) {
return status;
}
status = createPicture(m_picture, m_currentPTS);
if (status != YAMI_SUCCESS) {
ERROR("Could not create a VAAPI picture.");
return status;
}
m_picture->m_timeStamp = m_currentPTS;
SurfacePtr surf;
surf = m_picture->getSurface();
surf->setCrop(0, 0, m_videoFormatInfo.width, m_videoFormatInfo.height);
status = fillSliceParam();
if (status != YAMI_SUCCESS) {
ERROR("Failed to load VAAPI slice parameters.");
return status;
}
status = fillPictureParam();
if (status != YAMI_SUCCESS) {
ERROR("Failed to load VAAPI picture parameters");
return status;
}
status = loadQuantizationTables();
if (status != YAMI_SUCCESS) {
ERROR("Failed to load VAAPI quantization tables");
return status;
}
status = loadHuffmanTables();
if (status != YAMI_SUCCESS) {
ERROR("Failed to load VAAPI huffman tables");
return status;
}
if (!m_picture->decode())
return YAMI_FAIL;
status = outputPicture(m_picture);
if (status != YAMI_SUCCESS)
return status;
return YAMI_SUCCESS;
}
YamiStatus VaapiDecoderJPEG::reset(VideoConfigBuffer* buffer)
{
DEBUG("%s", __func__);
m_picture.reset();
m_impl.reset();
return VaapiDecoderBase::reset(buffer);
}
YamiStatus VaapiDecoderJPEG::ensureContext()
{
const FrameHeader::Shared frame = m_impl->frameHeader();
if (!frame->isBaseline) {
ERROR("Unsupported JPEG profile. Only JPEG Baseline is supported.");
return YAMI_FAIL;
}
if (!getFourcc(frame)) {
return YAMI_UNSUPPORTED;
}
if (setFormat(frame->imageWidth, frame->imageHeight, frame->imageWidth,
frame->imageHeight, JPEG_SURFACE_NUM, getFourcc(frame))) {
return YAMI_DECODE_FORMAT_CHANGE;
}
return ensureProfile(VAProfileJPEGBaseline);
}
}