[DEV] separate decoder
This commit is contained in:
parent
7d24032c1d
commit
0f2da05591
544
tools/player-video/appl/MediaDecoder.cpp
Normal file
544
tools/player-video/appl/MediaDecoder.cpp
Normal file
@ -0,0 +1,544 @@
|
||||
/** @file
|
||||
* @author Edouard DUPIN
|
||||
* @copyright 2016, Edouard DUPIN, all right reserved
|
||||
* @license GPL v3 (see license file)
|
||||
*/
|
||||
|
||||
#include <appl/debug.hpp>
|
||||
#include <ewol/widget/Widget.hpp>
|
||||
|
||||
|
||||
|
||||
#include <appl/debug.hpp>
|
||||
#include <appl/widget/VideoPlayer.hpp>
|
||||
#include <ewol/object/Manager.hpp>
|
||||
#include <etk/tool.hpp>
|
||||
#include <egami/egami.hpp>
|
||||
|
||||
static void unPlanar(void* _bufferOut, const void* _bufferIn, int32_t _nbSample, audio::format _format, int32_t _channelId, int32_t _nbChannel) {
|
||||
switch(_format) {
|
||||
case audio::format_int8: {
|
||||
const uint8_t* in = reinterpret_cast<const uint8_t*>(_bufferIn);
|
||||
uint8_t* out = reinterpret_cast<uint8_t*>(_bufferOut);
|
||||
for (int32_t sss=0; sss<_nbSample; ++sss) {
|
||||
out[sss*_nbChannel + _channelId] = in[sss];
|
||||
}
|
||||
return;
|
||||
}
|
||||
case audio::format_int16: {
|
||||
const int16_t* in = reinterpret_cast<const int16_t*>(_bufferIn);
|
||||
int16_t* out = reinterpret_cast<int16_t*>(_bufferOut);
|
||||
for (int32_t sss=0; sss<_nbSample; ++sss) {
|
||||
out[sss*_nbChannel + _channelId] = in[sss];
|
||||
}
|
||||
return;
|
||||
}
|
||||
case audio::format_int32: {
|
||||
const int32_t* in = reinterpret_cast<const int32_t*>(_bufferIn);
|
||||
int32_t* out = reinterpret_cast<int32_t*>(_bufferOut);
|
||||
for (int32_t sss=0; sss<_nbSample; ++sss) {
|
||||
out[sss*_nbChannel + _channelId] = in[sss];
|
||||
}
|
||||
return;
|
||||
}
|
||||
case audio::format_float: {
|
||||
const float* in = reinterpret_cast<const float*>(_bufferIn);
|
||||
float* out = reinterpret_cast<float*>(_bufferOut);
|
||||
for (int32_t sss=0; sss<_nbSample; ++sss) {
|
||||
out[sss*_nbChannel + _channelId] = in[sss];
|
||||
}
|
||||
return;
|
||||
}
|
||||
case audio::format_double: {
|
||||
const double* in = reinterpret_cast<const double*>(_bufferIn);
|
||||
double* out = reinterpret_cast<double*>(_bufferOut);
|
||||
for (int32_t sss=0; sss<_nbSample; ++sss) {
|
||||
out[sss*_nbChannel + _channelId] = in[sss];
|
||||
}
|
||||
return;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief get the next power 2 if the input
|
||||
* @param[in] value Value that we want the next power of 2
|
||||
* @return result value
|
||||
*/
|
||||
static int32_t nextP2(int32_t _value) {
|
||||
int32_t val=1;
|
||||
for (int32_t iii=1; iii<31; iii++) {
|
||||
if (_value <= val) {
|
||||
return val;
|
||||
}
|
||||
val *=2;
|
||||
}
|
||||
EWOL_CRITICAL("impossible CASE....");
|
||||
return val;
|
||||
}
|
||||
|
||||
void appl::BufferElementVideo::setSize(const ivec2& _size) {
|
||||
if (m_imagerealSize != _size) {
|
||||
// Resize the buffer:
|
||||
m_imagerealSize = _size;
|
||||
m_image.resize(ivec2(nextP2(_size.x()), nextP2(_size.y())));
|
||||
m_lineSize = m_image.getSize().x() * 3; // 3 is for RGBA
|
||||
//m_image.getSize();
|
||||
}
|
||||
}
|
||||
void appl::BufferElementAudio::configure(audio::format _format, uint32_t _sampleRate, int32_t _nbChannel, int32_t _nbSample) {
|
||||
// resize the buffer:
|
||||
m_buffer.resize(_nbSample*_nbChannel*audio::getFormatBytes(_format));
|
||||
m_format = _format;
|
||||
m_sampleRate = _sampleRate;
|
||||
m_map.resize(_nbChannel);
|
||||
switch(_nbChannel) {
|
||||
case 1:
|
||||
m_map[0] = audio::channel_frontCenter;
|
||||
break;
|
||||
case 2:
|
||||
m_map[0] = audio::channel_frontLeft;
|
||||
m_map[1] = audio::channel_frontRight;
|
||||
break;
|
||||
case 3:
|
||||
m_map[0] = audio::channel_frontLeft;
|
||||
m_map[1] = audio::channel_lfe;
|
||||
m_map[2] = audio::channel_frontRight;
|
||||
break;
|
||||
case 4:
|
||||
m_map[0] = audio::channel_frontLeft;
|
||||
m_map[1] = audio::channel_frontRight;
|
||||
m_map[2] = audio::channel_rearLeft;
|
||||
m_map[3] = audio::channel_rearRight;
|
||||
break;
|
||||
default:
|
||||
APPL_ERROR("not supportef nbChannel" << _nbChannel);
|
||||
}
|
||||
}
|
||||
|
||||
appl::MediaDecoder::MediaDecoder() {
|
||||
m_formatContext = nullptr;
|
||||
m_videoDecoderContext = nullptr;
|
||||
m_audioDecoderContext = nullptr;
|
||||
m_size = ivec2(0,0);
|
||||
m_videoStream = nullptr;
|
||||
m_audioStream = nullptr;
|
||||
|
||||
m_videoStream_idx = -1;
|
||||
m_audioStream_idx = -1;
|
||||
m_frame = nullptr;
|
||||
m_videoFrameCount = 0;
|
||||
m_audioFrameCount = 0;
|
||||
m_seek = -1;
|
||||
// output format convertion:
|
||||
m_convertContext = nullptr;
|
||||
m_audioPresent = false;
|
||||
m_audioFormat = audio::format_unknow;
|
||||
// Enable or disable frame reference counting.
|
||||
// You are not supposed to support both paths in your application but pick the one most appropriate to your needs.
|
||||
// Look for the use of refcount in this example to see what are the differences of API usage between them.
|
||||
m_refCount = false;
|
||||
}
|
||||
|
||||
int appl::MediaDecoder::decode_packet(int *_gotFrame, int _cached) {
|
||||
int ret = 0;
|
||||
int decoded = m_packet.size;
|
||||
*_gotFrame = 0;
|
||||
if (m_packet.stream_index == m_videoStream_idx) {
|
||||
// decode video frame
|
||||
ret = avcodec_decode_video2(m_videoDecoderContext, m_frame, _gotFrame, &m_packet);
|
||||
if (ret < 0) {
|
||||
APPL_ERROR("Error decoding video frame (" << av_err2str(ret) << ")");
|
||||
return ret;
|
||||
}
|
||||
if (*_gotFrame) {
|
||||
if ( m_frame->width != m_size.x()
|
||||
|| m_frame->height != m_size.y() ||
|
||||
m_frame->format != m_pixelFormat) {
|
||||
// To handle this change, one could call av_image_alloc again and decode the following frames into another rawvideo file.
|
||||
APPL_ERROR("Width, height and pixel format have to be constant in a rawvideo file, but the width, height or pixel format of the input video changed:");
|
||||
APPL_ERROR("old: size=" << m_size << " format=" << av_get_pix_fmt_name(m_pixelFormat));
|
||||
APPL_ERROR("new: size=" << ivec2(m_frame->width,m_frame->height) << " format=" << av_get_pix_fmt_name((enum AVPixelFormat)m_frame->format));
|
||||
return -1;
|
||||
}
|
||||
if (m_updateVideoTimeStampAfterSeek == true) {
|
||||
m_currentVideoTime = m_currentAudioTime;
|
||||
m_updateVideoTimeStampAfterSeek = false;
|
||||
m_seekApply = m_currentVideoTime; // => ready to display
|
||||
}
|
||||
echrono::Duration packetTime(double(m_frame->pkt_pts) * double(m_videoDecoderContext->time_base.num) / double(m_videoDecoderContext->time_base.den));
|
||||
APPL_INFO("video_frame " << (_cached?"(cached)":"")
|
||||
<< " n=" << m_videoFrameCount
|
||||
<< " coded_n=" << m_frame->coded_picture_number
|
||||
<< " pts=" << av_ts2timestr(m_frame->pkt_pts, &m_videoDecoderContext->time_base) << " " << packetTime);
|
||||
m_videoFrameCount++;
|
||||
int32_t slotId = videoGetEmptySlot();
|
||||
if (slotId == -1) {
|
||||
APPL_ERROR("an error occured when getting an empty slot for video");
|
||||
} else {
|
||||
m_videoPool[slotId].setSize(ivec2(m_frame->width, m_frame->height));
|
||||
uint8_t* dataPointer = (uint8_t*)(m_videoPool[slotId].m_image.getTextureDataPointer());
|
||||
// Convert Image in RGB:
|
||||
sws_scale(m_convertContext,
|
||||
(const uint8_t **)(m_frame->data),
|
||||
m_frame->linesize,
|
||||
0, m_frame->height,
|
||||
&dataPointer,
|
||||
&m_videoPool[slotId].m_lineSize);
|
||||
m_videoPool[slotId].m_id = m_videoFrameCount;
|
||||
m_videoPool[slotId].m_time = m_currentVideoTime;
|
||||
m_videoPool[slotId].m_duration = echrono::Duration(0, 1000000000.0/float(getFps(m_videoDecoderContext)));
|
||||
m_currentVideoTime += m_videoPool[slotId].m_duration;
|
||||
m_videoPool[slotId].m_isUsed = true;
|
||||
}
|
||||
}
|
||||
} else if (m_packet.stream_index == m_audioStream_idx) {
|
||||
// decode audio frame
|
||||
ret = avcodec_decode_audio4(m_audioDecoderContext, m_frame, _gotFrame, &m_packet);
|
||||
if (ret < 0) {
|
||||
APPL_ERROR("Error decoding audio frame (" << av_err2str(ret) << ")");
|
||||
return ret;
|
||||
}
|
||||
// Some audio decoders decode only part of the packet, and have to be called again with the remainder of the packet data.
|
||||
decoded = FFMIN(ret, m_packet.size);
|
||||
if (*_gotFrame) {
|
||||
echrono::Duration packetTime(double(m_frame->pkt_pts) * double(m_audioDecoderContext->time_base.num) / double(m_audioDecoderContext->time_base.den));
|
||||
if (m_updateVideoTimeStampAfterSeek == true) {
|
||||
// seek specific usecase ==> drop frame to have fast display
|
||||
m_currentAudioTime = packetTime;
|
||||
} else {
|
||||
APPL_INFO("audio_frame " << (_cached?"(cached)":"")
|
||||
<< " n=" << m_audioFrameCount
|
||||
<< " nb_samples=" << m_frame->nb_samples
|
||||
<< " pts=" << packetTime);
|
||||
m_audioFrameCount++;
|
||||
int32_t slotId = audioGetEmptySlot();
|
||||
if (slotId == -1) {
|
||||
APPL_ERROR("an error occured when getting an empty slot for audio");
|
||||
} else {
|
||||
//m_frame->channel_layout
|
||||
audio::format format = audio::format_unknow;
|
||||
switch(av_get_packed_sample_fmt((enum AVSampleFormat)m_frame->format)) {
|
||||
case AV_SAMPLE_FMT_U8: format = audio::format_int8; break;
|
||||
case AV_SAMPLE_FMT_S16: format = audio::format_int16; break;
|
||||
case AV_SAMPLE_FMT_S32: format = audio::format_int32; break;
|
||||
case AV_SAMPLE_FMT_FLT: format = audio::format_float; break;
|
||||
case AV_SAMPLE_FMT_DBL: format = audio::format_double; break;
|
||||
default: break;
|
||||
}
|
||||
if (format == audio::format_unknow) {
|
||||
APPL_ERROR("Unsupported audio format :" << m_frame->format << " ...");
|
||||
} else {
|
||||
// configure Buffer:
|
||||
m_audioPool[slotId].configure(format, m_frame->sample_rate, m_frame->channels, m_frame->nb_samples);
|
||||
if (av_sample_fmt_is_planar((enum AVSampleFormat)m_frame->format) == 1) {
|
||||
for (int32_t ccc=0; ccc<m_frame->channels; ++ccc) {
|
||||
unPlanar(&m_audioPool[slotId].m_buffer[0],
|
||||
m_frame->extended_data[ccc],
|
||||
m_frame->nb_samples,
|
||||
m_audioPool[slotId].m_format,
|
||||
ccc,
|
||||
m_frame->channels);
|
||||
}
|
||||
} else {
|
||||
// inject data in the buffer:
|
||||
memcpy(&m_audioPool[slotId].m_buffer[0], m_frame->extended_data[0], m_audioPool[slotId].m_buffer.size());
|
||||
}
|
||||
// We use the Time of the packet ==> better synchronisation when seeking
|
||||
m_currentAudioTime = packetTime;
|
||||
m_audioPool[slotId].m_id = m_audioFrameCount;
|
||||
m_audioPool[slotId].m_time = m_currentAudioTime;
|
||||
m_audioPool[slotId].m_duration = echrono::Duration(0,(1000000000.0*m_frame->nb_samples)/float(m_frame->sample_rate));
|
||||
m_currentAudioTime += m_audioPool[slotId].m_duration;
|
||||
m_audioPool[slotId].m_isUsed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we use frame reference counting, we own the data and need to de-reference it when we don't use it anymore
|
||||
if (*_gotFrame && m_refCount)
|
||||
av_frame_unref(m_frame);
|
||||
return decoded;
|
||||
}
|
||||
|
||||
int appl::MediaDecoder::open_codec_context(int *_streamId, AVFormatContext *_formatContext, enum AVMediaType _type) {
|
||||
int ret = 0;
|
||||
int stream_index = 0;
|
||||
AVStream *st = nullptr;
|
||||
AVCodecContext *dec_ctx = nullptr;
|
||||
AVCodec *dec = nullptr;
|
||||
AVDictionary *opts = nullptr;
|
||||
ret = av_find_best_stream(_formatContext, _type, -1, -1, nullptr, 0);
|
||||
if (ret < 0) {
|
||||
APPL_ERROR("Could not find " << av_get_media_type_string(_type) << " stream in input file '" << m_sourceFilename << "'");
|
||||
return ret;
|
||||
} else {
|
||||
stream_index = ret;
|
||||
st = _formatContext->streams[stream_index];
|
||||
// find decoder for the stream
|
||||
dec_ctx = st->codec;
|
||||
dec = avcodec_find_decoder(dec_ctx->codec_id);
|
||||
if (!dec) {
|
||||
APPL_ERROR("Failed to find " << av_get_media_type_string(_type) << " codec");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
// Init the decoders, with or without reference counting
|
||||
av_dict_set(&opts, "refcounted_frames", m_refCount ? "1" : "0", 0);
|
||||
//av_dict_set(&opts, "threads", "auto", 0);
|
||||
if ((ret = avcodec_open2(dec_ctx, dec, &opts)) < 0) {
|
||||
APPL_ERROR("Failed to open " << av_get_media_type_string(_type) << " codec");
|
||||
return ret;
|
||||
}
|
||||
*_streamId = stream_index;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
double appl::MediaDecoder::getFps(AVCodecContext *_avctx) {
|
||||
return 1.0 / av_q2d(_avctx->time_base) / FFMAX(_avctx->ticks_per_frame, 1);
|
||||
}
|
||||
|
||||
void appl::MediaDecoder::init(const std::string& _filename) {
|
||||
int ret = 0;
|
||||
// Enable or disable refcount:
|
||||
if (false) {
|
||||
m_refCount = true;
|
||||
}
|
||||
m_updateVideoTimeStampAfterSeek = false;
|
||||
m_sourceFilename = _filename;
|
||||
// register all formats and codecs
|
||||
av_register_all();
|
||||
// open input file, and allocate format context
|
||||
if (avformat_open_input(&m_formatContext, m_sourceFilename.c_str(), nullptr, nullptr) < 0) {
|
||||
APPL_ERROR("Could not open source file " << m_sourceFilename);
|
||||
exit(1);
|
||||
}
|
||||
// retrieve stream information
|
||||
if (avformat_find_stream_info(m_formatContext, nullptr) < 0) {
|
||||
APPL_ERROR("Could not find stream information");
|
||||
exit(1);
|
||||
}
|
||||
m_duration = echrono::Duration(double(m_formatContext->duration)/double(AV_TIME_BASE));
|
||||
APPL_INFO("Stream duration : " << m_duration);
|
||||
// Open Video decoder:
|
||||
if (open_codec_context(&m_videoStream_idx, m_formatContext, AVMEDIA_TYPE_VIDEO) >= 0) {
|
||||
m_videoStream = m_formatContext->streams[m_videoStream_idx];
|
||||
m_videoDecoderContext = m_videoStream->codec;
|
||||
// allocate image where the decoded image will be put
|
||||
m_size.setValue(m_videoDecoderContext->width, m_videoDecoderContext->height);
|
||||
m_pixelFormat = m_videoDecoderContext->pix_fmt;
|
||||
|
||||
m_videoPool.resize(10);
|
||||
APPL_INFO("Open video stream with property: size=" << m_size << " pixel format=" << av_get_pix_fmt_name(m_pixelFormat) << " fps=" << getFps(m_videoDecoderContext) << " tick/frame=" << m_videoDecoderContext->ticks_per_frame);
|
||||
// convert the image format:
|
||||
m_convertContext = sws_getContext(m_size.x(), m_size.y(), m_pixelFormat,
|
||||
m_size.x(), m_size.y(), AV_PIX_FMT_RGB24,
|
||||
0, 0, 0, 0);
|
||||
}
|
||||
// Open Audio Decoder:
|
||||
if (open_codec_context(&m_audioStream_idx, m_formatContext, AVMEDIA_TYPE_AUDIO) >= 0) {
|
||||
m_audioPresent = true;
|
||||
m_audioStream = m_formatContext->streams[m_audioStream_idx];
|
||||
m_audioDecoderContext = m_audioStream->codec;
|
||||
|
||||
m_audioPool.resize(80);
|
||||
|
||||
// Number of channels: m_audioDecoderContext->channels
|
||||
// Framerate: m_audioDecoderContext->sample_rate
|
||||
APPL_INFO("Open audio stream with audio property: " << int32_t(m_audioDecoderContext->channels) << " channel(s) & samplerate=" << m_audioDecoderContext->sample_rate << " Hz");
|
||||
|
||||
//m_frame->channel_layout
|
||||
m_audioSampleRate = m_audioDecoderContext->sample_rate;
|
||||
m_audioFormat = audio::format_unknow;
|
||||
switch(av_get_packed_sample_fmt(m_audioDecoderContext->sample_fmt)) {
|
||||
case AV_SAMPLE_FMT_U8: m_audioFormat = audio::format_int8; break;
|
||||
case AV_SAMPLE_FMT_S16: m_audioFormat = audio::format_int16; break;
|
||||
case AV_SAMPLE_FMT_S32: m_audioFormat = audio::format_int32; break;
|
||||
case AV_SAMPLE_FMT_FLT: m_audioFormat = audio::format_float; break;
|
||||
case AV_SAMPLE_FMT_DBL: m_audioFormat = audio::format_double; break;
|
||||
default:
|
||||
m_audioFormat = audio::format_unknow;
|
||||
break;
|
||||
}
|
||||
// TODO : Do it better:
|
||||
m_audioMap.resize(m_audioDecoderContext->channels);
|
||||
switch(m_audioDecoderContext->channels) {
|
||||
case 1:
|
||||
m_audioMap[0] = audio::channel_frontCenter;
|
||||
break;
|
||||
case 2:
|
||||
m_audioMap[0] = audio::channel_frontLeft;
|
||||
m_audioMap[1] = audio::channel_frontRight;
|
||||
break;
|
||||
case 3:
|
||||
m_audioMap[0] = audio::channel_frontLeft;
|
||||
m_audioMap[1] = audio::channel_lfe;
|
||||
m_audioMap[2] = audio::channel_frontRight;
|
||||
break;
|
||||
case 4:
|
||||
m_audioMap[0] = audio::channel_frontLeft;
|
||||
m_audioMap[1] = audio::channel_frontRight;
|
||||
m_audioMap[2] = audio::channel_rearLeft;
|
||||
m_audioMap[3] = audio::channel_rearRight;
|
||||
break;
|
||||
default:
|
||||
APPL_ERROR("not supportef nbChannel " << m_audioDecoderContext->channels);
|
||||
}
|
||||
APPL_PRINT("Audio configuration : " << m_audioMap << " " << m_audioFormat << " sampleRate=" <<m_audioSampleRate);
|
||||
}
|
||||
// dump input information to stderr
|
||||
av_dump_format(m_formatContext, 0, m_sourceFilename.c_str(), 0);
|
||||
if (!m_audioStream && !m_videoStream) {
|
||||
APPL_ERROR("Could not find audio or video stream in the input, aborting");
|
||||
ret = 1;
|
||||
return; // TODO : An error occured ... !!!!!
|
||||
}
|
||||
m_frame = av_frame_alloc();
|
||||
if (!m_frame) {
|
||||
APPL_ERROR("Could not allocate frame");
|
||||
ret = AVERROR(ENOMEM);
|
||||
return; // TODO : An error occured ... !!!!!
|
||||
}
|
||||
// initialize packet, set data to nullptr, let the demuxer fill it
|
||||
av_init_packet(&m_packet);
|
||||
m_packet.data = nullptr;
|
||||
m_packet.size = 0;
|
||||
}
|
||||
bool appl::MediaDecoder::onThreadCall() {
|
||||
if (m_seek >= echrono::Duration(0)) {
|
||||
// seek requested (create a copy to permit to update it in background):
|
||||
echrono::Duration tmpSeek = m_seek;
|
||||
m_seek = echrono::Duration(-1);
|
||||
applySeek(tmpSeek);
|
||||
}
|
||||
// check if we have space to decode data
|
||||
if ( ( m_videoPool.size() != 0
|
||||
&& videoGetEmptySlot() == -1)
|
||||
|| ( m_audioPool.size() != 0
|
||||
&& audioGetEmptySlot() == -1)
|
||||
) {
|
||||
// take some time to sleep the decoding ...
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(60/25));
|
||||
return false;
|
||||
}
|
||||
APPL_VERBOSE("Work on decoding");
|
||||
int gotFrame;
|
||||
// read frames from the file
|
||||
int ret = av_read_frame(m_formatContext, &m_packet);
|
||||
if (ret >= 0) {
|
||||
AVPacket orig_pkt = m_packet;
|
||||
do {
|
||||
ret = decode_packet(&gotFrame, 0);
|
||||
if (ret < 0) {
|
||||
break;
|
||||
}
|
||||
m_packet.data += ret;
|
||||
m_packet.size -= ret;
|
||||
} while (m_packet.size > 0);
|
||||
av_packet_unref(&orig_pkt);
|
||||
}
|
||||
return (ret < 0);
|
||||
}
|
||||
void appl::MediaDecoder::uninit() {
|
||||
// flush cached frames
|
||||
m_packet.data = nullptr;
|
||||
m_packet.size = 0;
|
||||
int gotFrame;
|
||||
do {
|
||||
decode_packet(&gotFrame, 1);
|
||||
} while (gotFrame);
|
||||
APPL_PRINT("Demuxing & Decoding succeeded...");
|
||||
avcodec_close(m_videoDecoderContext);
|
||||
avcodec_close(m_audioDecoderContext);
|
||||
avformat_close_input(&m_formatContext);
|
||||
av_frame_free(&m_frame);
|
||||
}
|
||||
|
||||
void appl::MediaDecoder::flushBuffer() {
|
||||
// flush all decoders ...
|
||||
avcodec_flush_buffers(m_audioStream->codec);
|
||||
avcodec_flush_buffers(m_videoStream->codec);
|
||||
// TODO : Protect this ...
|
||||
// Disable use of all buffer
|
||||
for (int32_t iii=0; iii<m_videoPool.size(); ++iii) {
|
||||
m_videoPool[iii].m_isUsed = false;
|
||||
}
|
||||
for (int32_t iii=0; iii<m_audioPool.size(); ++iii) {
|
||||
m_audioPool[iii].m_isUsed = false;
|
||||
}
|
||||
}
|
||||
|
||||
void appl::MediaDecoder::applySeek(echrono::Duration _time) {
|
||||
APPL_INFO("Apply seek : " << _time);
|
||||
flushBuffer();
|
||||
int64_t seekPos = int64_t(_time.toSeconds() * double(AV_TIME_BASE));
|
||||
|
||||
int32_t id = -1;
|
||||
echrono::Duration tmpPos;
|
||||
if (m_audioStream_idx>=0) {
|
||||
id = m_audioStream_idx;
|
||||
tmpPos = m_currentAudioTime;
|
||||
} else if (m_videoStream_idx>=0) {
|
||||
id = m_videoStream_idx;
|
||||
tmpPos = m_currentVideoTime;
|
||||
}
|
||||
|
||||
int64_t seekTarget = av_rescale_q(seekPos, AV_TIME_BASE_Q, m_formatContext->streams[id]->time_base);
|
||||
APPL_INFO("request seek at: " << seekPos << " and get position: " << seekTarget);
|
||||
int flags = _time < tmpPos ? AVSEEK_FLAG_BACKWARD : 0; // AVSEEK_FLAG_ANY
|
||||
if (av_seek_frame(m_formatContext, id, seekTarget, flags) < 0) {
|
||||
APPL_ERROR(" Unable to seek");
|
||||
return;
|
||||
}
|
||||
m_currentVideoTime = _time;
|
||||
m_currentAudioTime = _time;
|
||||
m_updateVideoTimeStampAfterSeek = true;
|
||||
APPL_INFO("Request seeking : " << _time << " done");
|
||||
}
|
||||
|
||||
int32_t appl::MediaDecoder::videoGetEmptySlot() {
|
||||
for (int32_t iii=0; iii<m_videoPool.size(); ++iii) {
|
||||
if (m_videoPool[iii].m_isUsed == false) {
|
||||
return iii;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t appl::MediaDecoder::audioGetEmptySlot() {
|
||||
for (int32_t iii=0; iii<m_audioPool.size(); ++iii) {
|
||||
if (m_audioPool[iii].m_isUsed == false) {
|
||||
return iii;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t appl::MediaDecoder::videoGetOlderSlot() {
|
||||
int32_t smallerId = 0x7FFFFFFE;
|
||||
int32_t findId = -1;
|
||||
for (int32_t iii=0; iii<m_videoPool.size(); ++iii) {
|
||||
if ( m_videoPool[iii].m_isUsed == true
|
||||
&& smallerId > m_videoPool[iii].m_id) {
|
||||
smallerId = m_videoPool[iii].m_id;
|
||||
findId = iii;
|
||||
}
|
||||
}
|
||||
return findId;
|
||||
}
|
||||
|
||||
int32_t appl::MediaDecoder::audioGetOlderSlot() {
|
||||
int32_t smallerId = 0x7FFFFFFF;
|
||||
int32_t findId = -1;
|
||||
for (int32_t iii=0; iii<m_audioPool.size(); ++iii) {
|
||||
if ( m_audioPool[iii].m_isUsed == true
|
||||
&& smallerId > m_audioPool[iii].m_id) {
|
||||
smallerId = m_audioPool[iii].m_id;
|
||||
findId = iii;
|
||||
}
|
||||
}
|
||||
return findId;
|
||||
}
|
135
tools/player-video/appl/MediaDecoder.hpp
Normal file
135
tools/player-video/appl/MediaDecoder.hpp
Normal file
@ -0,0 +1,135 @@
|
||||
/** @file
|
||||
* @author Edouard DUPIN
|
||||
* @copyright 2016, Edouard DUPIN, all right reserved
|
||||
* @license GPL v3 (see license file)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gale/Thread.hpp>
|
||||
#include <audio/channel.hpp>
|
||||
#include <audio/format.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/samplefmt.h>
|
||||
#include <libavutil/timestamp.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
namespace appl {
|
||||
class BufferElement {
|
||||
public:
|
||||
uint64_t m_id; //!< Id of the current image (must be unique)
|
||||
echrono::Duration m_time; //!< Current time of the Buffer Element
|
||||
echrono::Duration m_duration; //!< if the FPS is static ==> the duration can be set otherwise (0)
|
||||
bool m_isUsed; //!< This buffer is used
|
||||
BufferElement():
|
||||
m_id(0),
|
||||
m_isUsed(false) {
|
||||
|
||||
}
|
||||
virtual ~BufferElement() = default;
|
||||
};
|
||||
// class that contain all the element needed for a buffer image transfert:
|
||||
class BufferElementVideo : public appl::BufferElement {
|
||||
public:
|
||||
egami::Image m_image; //!< Image to manage internal data
|
||||
ivec2 m_imagerealSize; //!< Real size of the image, in OpenGL we need power of 2 border size.
|
||||
int32_t m_lineSize; //!< Size of a single line (in byte)
|
||||
void setSize(const ivec2& _newSize);
|
||||
BufferElementVideo():
|
||||
m_image(ivec2(32,32), egami::colorType::RGB8) {
|
||||
|
||||
}
|
||||
};
|
||||
class BufferElementAudio : public appl::BufferElement {
|
||||
public:
|
||||
std::vector<uint8_t> m_buffer; //!< raw audio data
|
||||
audio::format m_format; //!< Audio format buffer
|
||||
uint32_t m_sampleRate; //!< sample rate of the buffer
|
||||
std::vector<audio::channel> m_map; //!< Channel map of the buffer
|
||||
void configure(audio::format _format, uint32_t _sampleRate, int32_t _nbChannel, int32_t _nbSample);
|
||||
};
|
||||
|
||||
class MediaDecoder : public gale::Thread {
|
||||
public:
|
||||
echrono::Duration m_seekApply;
|
||||
private:
|
||||
echrono::Duration m_seek;
|
||||
void applySeek(echrono::Duration _time);
|
||||
echrono::Duration m_duration;
|
||||
public:
|
||||
echrono::Duration getDuration() {
|
||||
return m_duration;
|
||||
}
|
||||
public:
|
||||
std::vector<BufferElementAudio> m_audioPool;
|
||||
echrono::Duration m_currentAudioTime;
|
||||
std::vector<BufferElementVideo> m_videoPool;
|
||||
echrono::Duration m_currentVideoTime;
|
||||
bool m_updateVideoTimeStampAfterSeek;
|
||||
int32_t audioGetOlderSlot();
|
||||
int32_t videoGetOlderSlot();
|
||||
private:
|
||||
int32_t videoGetEmptySlot();
|
||||
int32_t audioGetEmptySlot();
|
||||
private:
|
||||
AVFormatContext* m_formatContext;
|
||||
AVCodecContext* m_videoDecoderContext;
|
||||
AVCodecContext* m_audioDecoderContext;
|
||||
ivec2 m_size;
|
||||
enum AVPixelFormat m_pixelFormat;
|
||||
AVStream *m_videoStream;
|
||||
AVStream *m_audioStream;
|
||||
std::string m_sourceFilename;
|
||||
|
||||
int32_t m_videoStream_idx;
|
||||
int32_t m_audioStream_idx;
|
||||
AVFrame *m_frame;
|
||||
AVPacket m_packet;
|
||||
int32_t m_videoFrameCount;
|
||||
int32_t m_audioFrameCount;
|
||||
|
||||
// output format convertion:
|
||||
SwsContext* m_convertContext;
|
||||
|
||||
// Enable or disable frame reference counting.
|
||||
// You are not supposed to support both paths in your application but pick the one most appropriate to your needs.
|
||||
// Look for the use of refcount in this example to see what are the differences of API usage between them.
|
||||
bool m_refCount;
|
||||
public:
|
||||
MediaDecoder();
|
||||
|
||||
int decode_packet(int *_gotFrame, int _cached);
|
||||
int open_codec_context(int *_streamId, AVFormatContext *_formatContext, enum AVMediaType _type);
|
||||
double getFps(AVCodecContext *_avctx);
|
||||
void init(const std::string& _filename);
|
||||
bool onThreadCall() override;
|
||||
void uninit();
|
||||
|
||||
bool m_audioPresent;
|
||||
audio::format m_audioFormat; //!< Audio format buffer
|
||||
uint32_t m_audioSampleRate; //!< sample rate of the buffer
|
||||
std::vector<audio::channel> m_audioMap; //!< Channel map of the buffer
|
||||
bool haveAudio() {
|
||||
return m_audioPresent;
|
||||
}
|
||||
uint32_t audioGetSampleRate() {
|
||||
return m_audioSampleRate;
|
||||
}
|
||||
std::vector<audio::channel> audioGetChannelMap() {
|
||||
return m_audioMap;
|
||||
}
|
||||
audio::format audioGetFormat() {
|
||||
return m_audioFormat;
|
||||
}
|
||||
void seek(const echrono::Duration& _time) {
|
||||
m_seek = _time;
|
||||
}
|
||||
void flushBuffer();
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -13,535 +13,6 @@
|
||||
#include <appl/widget/VideoPlayer.hpp>
|
||||
#include <ewol/object/Manager.hpp>
|
||||
#include <etk/tool.hpp>
|
||||
#include <egami/egami.hpp>
|
||||
|
||||
static void unPlanar(void* _bufferOut, const void* _bufferIn, int32_t _nbSample, audio::format _format, int32_t _channelId, int32_t _nbChannel) {
|
||||
switch(_format) {
|
||||
case audio::format_int8: {
|
||||
const uint8_t* in = reinterpret_cast<const uint8_t*>(_bufferIn);
|
||||
uint8_t* out = reinterpret_cast<uint8_t*>(_bufferOut);
|
||||
for (int32_t sss=0; sss<_nbSample; ++sss) {
|
||||
out[sss*_nbChannel + _channelId] = in[sss];
|
||||
}
|
||||
return;
|
||||
}
|
||||
case audio::format_int16: {
|
||||
const int16_t* in = reinterpret_cast<const int16_t*>(_bufferIn);
|
||||
int16_t* out = reinterpret_cast<int16_t*>(_bufferOut);
|
||||
for (int32_t sss=0; sss<_nbSample; ++sss) {
|
||||
out[sss*_nbChannel + _channelId] = in[sss];
|
||||
}
|
||||
return;
|
||||
}
|
||||
case audio::format_int32: {
|
||||
const int32_t* in = reinterpret_cast<const int32_t*>(_bufferIn);
|
||||
int32_t* out = reinterpret_cast<int32_t*>(_bufferOut);
|
||||
for (int32_t sss=0; sss<_nbSample; ++sss) {
|
||||
out[sss*_nbChannel + _channelId] = in[sss];
|
||||
}
|
||||
return;
|
||||
}
|
||||
case audio::format_float: {
|
||||
const float* in = reinterpret_cast<const float*>(_bufferIn);
|
||||
float* out = reinterpret_cast<float*>(_bufferOut);
|
||||
for (int32_t sss=0; sss<_nbSample; ++sss) {
|
||||
out[sss*_nbChannel + _channelId] = in[sss];
|
||||
}
|
||||
return;
|
||||
}
|
||||
case audio::format_double: {
|
||||
const double* in = reinterpret_cast<const double*>(_bufferIn);
|
||||
double* out = reinterpret_cast<double*>(_bufferOut);
|
||||
for (int32_t sss=0; sss<_nbSample; ++sss) {
|
||||
out[sss*_nbChannel + _channelId] = in[sss];
|
||||
}
|
||||
return;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief get the next power 2 if the input
|
||||
* @param[in] value Value that we want the next power of 2
|
||||
* @return result value
|
||||
*/
|
||||
static int32_t nextP2(int32_t _value) {
|
||||
int32_t val=1;
|
||||
for (int32_t iii=1; iii<31; iii++) {
|
||||
if (_value <= val) {
|
||||
return val;
|
||||
}
|
||||
val *=2;
|
||||
}
|
||||
EWOL_CRITICAL("impossible CASE....");
|
||||
return val;
|
||||
}
|
||||
|
||||
void appl::BufferElementVideo::setSize(const ivec2& _size) {
|
||||
if (m_imagerealSize != _size) {
|
||||
// Resize the buffer:
|
||||
m_imagerealSize = _size;
|
||||
m_image.resize(ivec2(nextP2(_size.x()), nextP2(_size.y())));
|
||||
m_lineSize = m_image.getSize().x() * 3; // 3 is for RGBA
|
||||
//m_image.getSize();
|
||||
}
|
||||
}
|
||||
void appl::BufferElementAudio::configure(audio::format _format, uint32_t _sampleRate, int32_t _nbChannel, int32_t _nbSample) {
|
||||
// resize the buffer:
|
||||
m_buffer.resize(_nbSample*_nbChannel*audio::getFormatBytes(_format));
|
||||
m_format = _format;
|
||||
m_sampleRate = _sampleRate;
|
||||
m_map.resize(_nbChannel);
|
||||
switch(_nbChannel) {
|
||||
case 1:
|
||||
m_map[0] = audio::channel_frontCenter;
|
||||
break;
|
||||
case 2:
|
||||
m_map[0] = audio::channel_frontLeft;
|
||||
m_map[1] = audio::channel_frontRight;
|
||||
break;
|
||||
case 3:
|
||||
m_map[0] = audio::channel_frontLeft;
|
||||
m_map[1] = audio::channel_lfe;
|
||||
m_map[2] = audio::channel_frontRight;
|
||||
break;
|
||||
case 4:
|
||||
m_map[0] = audio::channel_frontLeft;
|
||||
m_map[1] = audio::channel_frontRight;
|
||||
m_map[2] = audio::channel_rearLeft;
|
||||
m_map[3] = audio::channel_rearRight;
|
||||
break;
|
||||
default:
|
||||
APPL_ERROR("not supportef nbChannel" << _nbChannel);
|
||||
}
|
||||
}
|
||||
|
||||
appl::Decoder::Decoder() {
|
||||
m_formatContext = nullptr;
|
||||
m_videoDecoderContext = nullptr;
|
||||
m_audioDecoderContext = nullptr;
|
||||
m_size = ivec2(0,0);
|
||||
m_videoStream = nullptr;
|
||||
m_audioStream = nullptr;
|
||||
|
||||
m_videoStream_idx = -1;
|
||||
m_audioStream_idx = -1;
|
||||
m_frame = nullptr;
|
||||
m_videoFrameCount = 0;
|
||||
m_audioFrameCount = 0;
|
||||
m_seek = -1;
|
||||
// output format convertion:
|
||||
m_convertContext = nullptr;
|
||||
m_audioPresent = false;
|
||||
m_audioFormat = audio::format_unknow;
|
||||
// Enable or disable frame reference counting.
|
||||
// You are not supposed to support both paths in your application but pick the one most appropriate to your needs.
|
||||
// Look for the use of refcount in this example to see what are the differences of API usage between them.
|
||||
m_refCount = false;
|
||||
}
|
||||
|
||||
int appl::Decoder::decode_packet(int *_gotFrame, int _cached) {
|
||||
int ret = 0;
|
||||
int decoded = m_packet.size;
|
||||
*_gotFrame = 0;
|
||||
if (m_packet.stream_index == m_videoStream_idx) {
|
||||
// decode video frame
|
||||
ret = avcodec_decode_video2(m_videoDecoderContext, m_frame, _gotFrame, &m_packet);
|
||||
if (ret < 0) {
|
||||
APPL_ERROR("Error decoding video frame (" << av_err2str(ret) << ")");
|
||||
return ret;
|
||||
}
|
||||
if (*_gotFrame) {
|
||||
if ( m_frame->width != m_size.x()
|
||||
|| m_frame->height != m_size.y() ||
|
||||
m_frame->format != m_pixelFormat) {
|
||||
// To handle this change, one could call av_image_alloc again and decode the following frames into another rawvideo file.
|
||||
APPL_ERROR("Width, height and pixel format have to be constant in a rawvideo file, but the width, height or pixel format of the input video changed:");
|
||||
APPL_ERROR("old: size=" << m_size << " format=" << av_get_pix_fmt_name(m_pixelFormat));
|
||||
APPL_ERROR("new: size=" << ivec2(m_frame->width,m_frame->height) << " format=" << av_get_pix_fmt_name((enum AVPixelFormat)m_frame->format));
|
||||
return -1;
|
||||
}
|
||||
if (m_updateVideoTimeStampAfterSeek == true) {
|
||||
m_currentVideoTime = m_currentAudioTime;
|
||||
m_updateVideoTimeStampAfterSeek = false;
|
||||
m_seekApply = m_currentVideoTime; // => ready to display
|
||||
}
|
||||
echrono::Duration packetTime(double(m_frame->pkt_pts) * double(m_videoDecoderContext->time_base.num) / double(m_videoDecoderContext->time_base.den));
|
||||
APPL_INFO("video_frame " << (_cached?"(cached)":"")
|
||||
<< " n=" << m_videoFrameCount
|
||||
<< " coded_n=" << m_frame->coded_picture_number
|
||||
<< " pts=" << av_ts2timestr(m_frame->pkt_pts, &m_videoDecoderContext->time_base) << " " << packetTime);
|
||||
m_videoFrameCount++;
|
||||
int32_t slotId = videoGetEmptySlot();
|
||||
if (slotId == -1) {
|
||||
APPL_ERROR("an error occured when getting an empty slot for video");
|
||||
} else {
|
||||
m_videoPool[slotId].setSize(ivec2(m_frame->width, m_frame->height));
|
||||
uint8_t* dataPointer = (uint8_t*)(m_videoPool[slotId].m_image.getTextureDataPointer());
|
||||
// Convert Image in RGB:
|
||||
sws_scale(m_convertContext,
|
||||
(const uint8_t **)(m_frame->data),
|
||||
m_frame->linesize,
|
||||
0, m_frame->height,
|
||||
&dataPointer,
|
||||
&m_videoPool[slotId].m_lineSize);
|
||||
m_videoPool[slotId].m_id = m_videoFrameCount;
|
||||
m_videoPool[slotId].m_time = m_currentVideoTime;
|
||||
m_videoPool[slotId].m_duration = echrono::Duration(0, 1000000000.0/float(getFps(m_videoDecoderContext)));
|
||||
m_currentVideoTime += m_videoPool[slotId].m_duration;
|
||||
m_videoPool[slotId].m_isUsed = true;
|
||||
}
|
||||
}
|
||||
} else if (m_packet.stream_index == m_audioStream_idx) {
|
||||
// decode audio frame
|
||||
ret = avcodec_decode_audio4(m_audioDecoderContext, m_frame, _gotFrame, &m_packet);
|
||||
if (ret < 0) {
|
||||
APPL_ERROR("Error decoding audio frame (" << av_err2str(ret) << ")");
|
||||
return ret;
|
||||
}
|
||||
// Some audio decoders decode only part of the packet, and have to be called again with the remainder of the packet data.
|
||||
decoded = FFMIN(ret, m_packet.size);
|
||||
if (*_gotFrame) {
|
||||
echrono::Duration packetTime(double(m_frame->pkt_pts) * double(m_audioDecoderContext->time_base.num) / double(m_audioDecoderContext->time_base.den));
|
||||
if (m_updateVideoTimeStampAfterSeek == true) {
|
||||
// seek specific usecase ==> drop frame to have fast display
|
||||
m_currentAudioTime = packetTime;
|
||||
} else {
|
||||
APPL_INFO("audio_frame " << (_cached?"(cached)":"")
|
||||
<< " n=" << m_audioFrameCount
|
||||
<< " nb_samples=" << m_frame->nb_samples
|
||||
<< " pts=" << packetTime);
|
||||
m_audioFrameCount++;
|
||||
int32_t slotId = audioGetEmptySlot();
|
||||
if (slotId == -1) {
|
||||
APPL_ERROR("an error occured when getting an empty slot for audio");
|
||||
} else {
|
||||
//m_frame->channel_layout
|
||||
audio::format format = audio::format_unknow;
|
||||
switch(av_get_packed_sample_fmt((enum AVSampleFormat)m_frame->format)) {
|
||||
case AV_SAMPLE_FMT_U8: format = audio::format_int8; break;
|
||||
case AV_SAMPLE_FMT_S16: format = audio::format_int16; break;
|
||||
case AV_SAMPLE_FMT_S32: format = audio::format_int32; break;
|
||||
case AV_SAMPLE_FMT_FLT: format = audio::format_float; break;
|
||||
case AV_SAMPLE_FMT_DBL: format = audio::format_double; break;
|
||||
default: break;
|
||||
}
|
||||
if (format == audio::format_unknow) {
|
||||
APPL_ERROR("Unsupported audio format :" << m_frame->format << " ...");
|
||||
} else {
|
||||
// configure Buffer:
|
||||
m_audioPool[slotId].configure(format, m_frame->sample_rate, m_frame->channels, m_frame->nb_samples);
|
||||
if (av_sample_fmt_is_planar((enum AVSampleFormat)m_frame->format) == 1) {
|
||||
for (int32_t ccc=0; ccc<m_frame->channels; ++ccc) {
|
||||
unPlanar(&m_audioPool[slotId].m_buffer[0],
|
||||
m_frame->extended_data[ccc],
|
||||
m_frame->nb_samples,
|
||||
m_audioPool[slotId].m_format,
|
||||
ccc,
|
||||
m_frame->channels);
|
||||
}
|
||||
} else {
|
||||
// inject data in the buffer:
|
||||
memcpy(&m_audioPool[slotId].m_buffer[0], m_frame->extended_data[0], m_audioPool[slotId].m_buffer.size());
|
||||
}
|
||||
// We use the Time of the packet ==> better synchronisation when seeking
|
||||
m_currentAudioTime = packetTime;
|
||||
m_audioPool[slotId].m_id = m_audioFrameCount;
|
||||
m_audioPool[slotId].m_time = m_currentAudioTime;
|
||||
m_audioPool[slotId].m_duration = echrono::Duration(0,(1000000000.0*m_frame->nb_samples)/float(m_frame->sample_rate));
|
||||
m_currentAudioTime += m_audioPool[slotId].m_duration;
|
||||
m_audioPool[slotId].m_isUsed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we use frame reference counting, we own the data and need to de-reference it when we don't use it anymore
|
||||
if (*_gotFrame && m_refCount)
|
||||
av_frame_unref(m_frame);
|
||||
return decoded;
|
||||
}
|
||||
|
||||
int appl::Decoder::open_codec_context(int *_streamId, AVFormatContext *_formatContext, enum AVMediaType _type) {
|
||||
int ret = 0;
|
||||
int stream_index = 0;
|
||||
AVStream *st = nullptr;
|
||||
AVCodecContext *dec_ctx = nullptr;
|
||||
AVCodec *dec = nullptr;
|
||||
AVDictionary *opts = nullptr;
|
||||
ret = av_find_best_stream(_formatContext, _type, -1, -1, nullptr, 0);
|
||||
if (ret < 0) {
|
||||
APPL_ERROR("Could not find " << av_get_media_type_string(_type) << " stream in input file '" << m_sourceFilename << "'");
|
||||
return ret;
|
||||
} else {
|
||||
stream_index = ret;
|
||||
st = _formatContext->streams[stream_index];
|
||||
// find decoder for the stream
|
||||
dec_ctx = st->codec;
|
||||
dec = avcodec_find_decoder(dec_ctx->codec_id);
|
||||
if (!dec) {
|
||||
APPL_ERROR("Failed to find " << av_get_media_type_string(_type) << " codec");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
// Init the decoders, with or without reference counting
|
||||
av_dict_set(&opts, "refcounted_frames", m_refCount ? "1" : "0", 0);
|
||||
//av_dict_set(&opts, "threads", "auto", 0);
|
||||
if ((ret = avcodec_open2(dec_ctx, dec, &opts)) < 0) {
|
||||
APPL_ERROR("Failed to open " << av_get_media_type_string(_type) << " codec");
|
||||
return ret;
|
||||
}
|
||||
*_streamId = stream_index;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
double appl::Decoder::getFps(AVCodecContext *_avctx) {
|
||||
return 1.0 / av_q2d(_avctx->time_base) / FFMAX(_avctx->ticks_per_frame, 1);
|
||||
}
|
||||
|
||||
void appl::Decoder::init(const std::string& _filename) {
|
||||
int ret = 0;
|
||||
// Enable or disable refcount:
|
||||
if (false) {
|
||||
m_refCount = true;
|
||||
}
|
||||
m_updateVideoTimeStampAfterSeek = false;
|
||||
m_sourceFilename = _filename;
|
||||
// register all formats and codecs
|
||||
av_register_all();
|
||||
// open input file, and allocate format context
|
||||
if (avformat_open_input(&m_formatContext, m_sourceFilename.c_str(), nullptr, nullptr) < 0) {
|
||||
APPL_ERROR("Could not open source file " << m_sourceFilename);
|
||||
exit(1);
|
||||
}
|
||||
// retrieve stream information
|
||||
if (avformat_find_stream_info(m_formatContext, nullptr) < 0) {
|
||||
APPL_ERROR("Could not find stream information");
|
||||
exit(1);
|
||||
}
|
||||
m_duration = echrono::Duration(double(m_formatContext->duration)/double(AV_TIME_BASE));
|
||||
APPL_INFO("Stream duration : " << m_duration);
|
||||
// Open Video decoder:
|
||||
if (open_codec_context(&m_videoStream_idx, m_formatContext, AVMEDIA_TYPE_VIDEO) >= 0) {
|
||||
m_videoStream = m_formatContext->streams[m_videoStream_idx];
|
||||
m_videoDecoderContext = m_videoStream->codec;
|
||||
// allocate image where the decoded image will be put
|
||||
m_size.setValue(m_videoDecoderContext->width, m_videoDecoderContext->height);
|
||||
m_pixelFormat = m_videoDecoderContext->pix_fmt;
|
||||
|
||||
m_videoPool.resize(10);
|
||||
APPL_INFO("Open video stream with property: size=" << m_size << " pixel format=" << av_get_pix_fmt_name(m_pixelFormat) << " fps=" << getFps(m_videoDecoderContext) << " tick/frame=" << m_videoDecoderContext->ticks_per_frame);
|
||||
// convert the image format:
|
||||
m_convertContext = sws_getContext(m_size.x(), m_size.y(), m_pixelFormat,
|
||||
m_size.x(), m_size.y(), AV_PIX_FMT_RGB24,
|
||||
0, 0, 0, 0);
|
||||
}
|
||||
// Open Audio Decoder:
|
||||
if (open_codec_context(&m_audioStream_idx, m_formatContext, AVMEDIA_TYPE_AUDIO) >= 0) {
|
||||
m_audioPresent = true;
|
||||
m_audioStream = m_formatContext->streams[m_audioStream_idx];
|
||||
m_audioDecoderContext = m_audioStream->codec;
|
||||
|
||||
m_audioPool.resize(80);
|
||||
|
||||
// Number of channels: m_audioDecoderContext->channels
|
||||
// Framerate: m_audioDecoderContext->sample_rate
|
||||
APPL_INFO("Open audio stream with audio property: " << int32_t(m_audioDecoderContext->channels) << " channel(s) & samplerate=" << m_audioDecoderContext->sample_rate << " Hz");
|
||||
|
||||
//m_frame->channel_layout
|
||||
m_audioSampleRate = m_audioDecoderContext->sample_rate;
|
||||
m_audioFormat = audio::format_unknow;
|
||||
switch(av_get_packed_sample_fmt(m_audioDecoderContext->sample_fmt)) {
|
||||
case AV_SAMPLE_FMT_U8: m_audioFormat = audio::format_int8; break;
|
||||
case AV_SAMPLE_FMT_S16: m_audioFormat = audio::format_int16; break;
|
||||
case AV_SAMPLE_FMT_S32: m_audioFormat = audio::format_int32; break;
|
||||
case AV_SAMPLE_FMT_FLT: m_audioFormat = audio::format_float; break;
|
||||
case AV_SAMPLE_FMT_DBL: m_audioFormat = audio::format_double; break;
|
||||
default:
|
||||
m_audioFormat = audio::format_unknow;
|
||||
break;
|
||||
}
|
||||
// TODO : Do it better:
|
||||
m_audioMap.resize(m_audioDecoderContext->channels);
|
||||
switch(m_audioDecoderContext->channels) {
|
||||
case 1:
|
||||
m_audioMap[0] = audio::channel_frontCenter;
|
||||
break;
|
||||
case 2:
|
||||
m_audioMap[0] = audio::channel_frontLeft;
|
||||
m_audioMap[1] = audio::channel_frontRight;
|
||||
break;
|
||||
case 3:
|
||||
m_audioMap[0] = audio::channel_frontLeft;
|
||||
m_audioMap[1] = audio::channel_lfe;
|
||||
m_audioMap[2] = audio::channel_frontRight;
|
||||
break;
|
||||
case 4:
|
||||
m_audioMap[0] = audio::channel_frontLeft;
|
||||
m_audioMap[1] = audio::channel_frontRight;
|
||||
m_audioMap[2] = audio::channel_rearLeft;
|
||||
m_audioMap[3] = audio::channel_rearRight;
|
||||
break;
|
||||
default:
|
||||
APPL_ERROR("not supportef nbChannel " << m_audioDecoderContext->channels);
|
||||
}
|
||||
APPL_PRINT("Audio configuration : " << m_audioMap << " " << m_audioFormat << " sampleRate=" <<m_audioSampleRate);
|
||||
}
|
||||
// dump input information to stderr
|
||||
av_dump_format(m_formatContext, 0, m_sourceFilename.c_str(), 0);
|
||||
if (!m_audioStream && !m_videoStream) {
|
||||
APPL_ERROR("Could not find audio or video stream in the input, aborting");
|
||||
ret = 1;
|
||||
return; // TODO : An error occured ... !!!!!
|
||||
}
|
||||
m_frame = av_frame_alloc();
|
||||
if (!m_frame) {
|
||||
APPL_ERROR("Could not allocate frame");
|
||||
ret = AVERROR(ENOMEM);
|
||||
return; // TODO : An error occured ... !!!!!
|
||||
}
|
||||
// initialize packet, set data to nullptr, let the demuxer fill it
|
||||
av_init_packet(&m_packet);
|
||||
m_packet.data = nullptr;
|
||||
m_packet.size = 0;
|
||||
}
|
||||
bool appl::Decoder::onThreadCall() {
|
||||
if (m_seek >= echrono::Duration(0)) {
|
||||
// seek requested (create a copy to permit to update it in background):
|
||||
echrono::Duration tmpSeek = m_seek;
|
||||
m_seek = echrono::Duration(-1);
|
||||
applySeek(tmpSeek);
|
||||
}
|
||||
// check if we have space to decode data
|
||||
if ( ( m_videoPool.size() != 0
|
||||
&& videoGetEmptySlot() == -1)
|
||||
|| ( m_audioPool.size() != 0
|
||||
&& audioGetEmptySlot() == -1)
|
||||
) {
|
||||
// take some time to sleep the decoding ...
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(60/25));
|
||||
return false;
|
||||
}
|
||||
APPL_VERBOSE("Work on decoding");
|
||||
int gotFrame;
|
||||
// read frames from the file
|
||||
int ret = av_read_frame(m_formatContext, &m_packet);
|
||||
if (ret >= 0) {
|
||||
AVPacket orig_pkt = m_packet;
|
||||
do {
|
||||
ret = decode_packet(&gotFrame, 0);
|
||||
if (ret < 0) {
|
||||
break;
|
||||
}
|
||||
m_packet.data += ret;
|
||||
m_packet.size -= ret;
|
||||
} while (m_packet.size > 0);
|
||||
av_packet_unref(&orig_pkt);
|
||||
}
|
||||
return (ret < 0);
|
||||
}
|
||||
void appl::Decoder::uninit() {
|
||||
// flush cached frames
|
||||
m_packet.data = nullptr;
|
||||
m_packet.size = 0;
|
||||
int gotFrame;
|
||||
do {
|
||||
decode_packet(&gotFrame, 1);
|
||||
} while (gotFrame);
|
||||
APPL_PRINT("Demuxing & Decoding succeeded...");
|
||||
avcodec_close(m_videoDecoderContext);
|
||||
avcodec_close(m_audioDecoderContext);
|
||||
avformat_close_input(&m_formatContext);
|
||||
av_frame_free(&m_frame);
|
||||
}
|
||||
|
||||
void appl::Decoder::flushBuffer() {
|
||||
// flush all decoders ...
|
||||
avcodec_flush_buffers(m_audioStream->codec);
|
||||
avcodec_flush_buffers(m_videoStream->codec);
|
||||
// TODO : Protect this ...
|
||||
// Disable use of all buffer
|
||||
for (int32_t iii=0; iii<m_videoPool.size(); ++iii) {
|
||||
m_videoPool[iii].m_isUsed = false;
|
||||
}
|
||||
for (int32_t iii=0; iii<m_audioPool.size(); ++iii) {
|
||||
m_audioPool[iii].m_isUsed = false;
|
||||
}
|
||||
}
|
||||
|
||||
void appl::Decoder::applySeek(echrono::Duration _time) {
|
||||
APPL_INFO("Apply seek : " << _time);
|
||||
flushBuffer();
|
||||
int64_t seekPos = int64_t(_time.toSeconds() * double(AV_TIME_BASE));
|
||||
|
||||
int32_t id = -1;
|
||||
echrono::Duration tmpPos;
|
||||
if (m_audioStream_idx>=0) {
|
||||
id = m_audioStream_idx;
|
||||
tmpPos = m_currentAudioTime;
|
||||
} else if (m_videoStream_idx>=0) {
|
||||
id = m_videoStream_idx;
|
||||
tmpPos = m_currentVideoTime;
|
||||
}
|
||||
|
||||
int64_t seekTarget = av_rescale_q(seekPos, AV_TIME_BASE_Q, m_formatContext->streams[id]->time_base);
|
||||
APPL_INFO("request seek at: " << seekPos << " and get position: " << seekTarget);
|
||||
int flags = _time < tmpPos ? AVSEEK_FLAG_BACKWARD : 0; // AVSEEK_FLAG_ANY
|
||||
if (av_seek_frame(m_formatContext, id, seekTarget, flags) < 0) {
|
||||
APPL_ERROR(" Unable to seek");
|
||||
return;
|
||||
}
|
||||
m_currentVideoTime = _time;
|
||||
m_currentAudioTime = _time;
|
||||
m_updateVideoTimeStampAfterSeek = true;
|
||||
APPL_INFO("Request seeking : " << _time << " done");
|
||||
}
|
||||
|
||||
int32_t appl::Decoder::videoGetEmptySlot() {
|
||||
for (int32_t iii=0; iii<m_videoPool.size(); ++iii) {
|
||||
if (m_videoPool[iii].m_isUsed == false) {
|
||||
return iii;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t appl::Decoder::audioGetEmptySlot() {
|
||||
for (int32_t iii=0; iii<m_audioPool.size(); ++iii) {
|
||||
if (m_audioPool[iii].m_isUsed == false) {
|
||||
return iii;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t appl::Decoder::videoGetOlderSlot() {
|
||||
int32_t smallerId = 0x7FFFFFFE;
|
||||
int32_t findId = -1;
|
||||
for (int32_t iii=0; iii<m_videoPool.size(); ++iii) {
|
||||
if ( m_videoPool[iii].m_isUsed == true
|
||||
&& smallerId > m_videoPool[iii].m_id) {
|
||||
smallerId = m_videoPool[iii].m_id;
|
||||
findId = iii;
|
||||
}
|
||||
}
|
||||
return findId;
|
||||
}
|
||||
|
||||
int32_t appl::Decoder::audioGetOlderSlot() {
|
||||
int32_t smallerId = 0x7FFFFFFF;
|
||||
int32_t findId = -1;
|
||||
for (int32_t iii=0; iii<m_audioPool.size(); ++iii) {
|
||||
if ( m_audioPool[iii].m_isUsed == true
|
||||
&& smallerId > m_audioPool[iii].m_id) {
|
||||
smallerId = m_audioPool[iii].m_id;
|
||||
findId = iii;
|
||||
}
|
||||
}
|
||||
return findId;
|
||||
}
|
||||
|
||||
// VBO table property:
|
||||
const int32_t appl::widget::VideoDisplay::m_vboIdCoord(0);
|
||||
|
@ -16,128 +16,8 @@
|
||||
#include <audio/river/river.hpp>
|
||||
#include <audio/river/Manager.hpp>
|
||||
#include <audio/river/Interface.hpp>
|
||||
#include <appl/MediaDecoder.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/samplefmt.h>
|
||||
#include <libavutil/timestamp.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
namespace appl {
|
||||
class BufferElement {
|
||||
public:
|
||||
uint64_t m_id; //!< Id of the current image (must be unique)
|
||||
echrono::Duration m_time; //!< Current time of the Buffer Element
|
||||
echrono::Duration m_duration; //!< if the FPS is static ==> the duration can be set otherwise (0)
|
||||
bool m_isUsed; //!< This buffer is used
|
||||
BufferElement():
|
||||
m_id(0),
|
||||
m_isUsed(false) {
|
||||
|
||||
}
|
||||
virtual ~BufferElement() = default;
|
||||
};
|
||||
// class that contain all the element needed for a buffer image transfert:
|
||||
class BufferElementVideo : public appl::BufferElement {
|
||||
public:
|
||||
egami::Image m_image; //!< Image to manage internal data
|
||||
ivec2 m_imagerealSize; //!< Real size of the image, in OpenGL we need power of 2 border size.
|
||||
int32_t m_lineSize; //!< Size of a single line (in byte)
|
||||
void setSize(const ivec2& _newSize);
|
||||
BufferElementVideo():
|
||||
m_image(ivec2(32,32), egami::colorType::RGB8) {
|
||||
|
||||
}
|
||||
};
|
||||
class BufferElementAudio : public appl::BufferElement {
|
||||
public:
|
||||
std::vector<uint8_t> m_buffer; //!< raw audio data
|
||||
audio::format m_format; //!< Audio format buffer
|
||||
uint32_t m_sampleRate; //!< sample rate of the buffer
|
||||
std::vector<audio::channel> m_map; //!< Channel map of the buffer
|
||||
void configure(audio::format _format, uint32_t _sampleRate, int32_t _nbChannel, int32_t _nbSample);
|
||||
};
|
||||
|
||||
class Decoder : public gale::Thread {
|
||||
public:
|
||||
echrono::Duration m_seekApply;
|
||||
private:
|
||||
echrono::Duration m_seek;
|
||||
void applySeek(echrono::Duration _time);
|
||||
echrono::Duration m_duration;
|
||||
public:
|
||||
echrono::Duration getDuration() {
|
||||
return m_duration;
|
||||
}
|
||||
public:
|
||||
std::vector<BufferElementAudio> m_audioPool;
|
||||
echrono::Duration m_currentAudioTime;
|
||||
std::vector<BufferElementVideo> m_videoPool;
|
||||
echrono::Duration m_currentVideoTime;
|
||||
bool m_updateVideoTimeStampAfterSeek;
|
||||
int32_t audioGetOlderSlot();
|
||||
int32_t videoGetOlderSlot();
|
||||
private:
|
||||
int32_t videoGetEmptySlot();
|
||||
int32_t audioGetEmptySlot();
|
||||
private:
|
||||
AVFormatContext* m_formatContext;
|
||||
AVCodecContext* m_videoDecoderContext;
|
||||
AVCodecContext* m_audioDecoderContext;
|
||||
ivec2 m_size;
|
||||
enum AVPixelFormat m_pixelFormat;
|
||||
AVStream *m_videoStream;
|
||||
AVStream *m_audioStream;
|
||||
std::string m_sourceFilename;
|
||||
|
||||
int32_t m_videoStream_idx;
|
||||
int32_t m_audioStream_idx;
|
||||
AVFrame *m_frame;
|
||||
AVPacket m_packet;
|
||||
int32_t m_videoFrameCount;
|
||||
int32_t m_audioFrameCount;
|
||||
|
||||
// output format convertion:
|
||||
SwsContext* m_convertContext;
|
||||
|
||||
// Enable or disable frame reference counting.
|
||||
// You are not supposed to support both paths in your application but pick the one most appropriate to your needs.
|
||||
// Look for the use of refcount in this example to see what are the differences of API usage between them.
|
||||
bool m_refCount;
|
||||
public:
|
||||
Decoder();
|
||||
|
||||
int decode_packet(int *_gotFrame, int _cached);
|
||||
int open_codec_context(int *_streamId, AVFormatContext *_formatContext, enum AVMediaType _type);
|
||||
double getFps(AVCodecContext *_avctx);
|
||||
void init(const std::string& _filename);
|
||||
bool onThreadCall() override;
|
||||
void uninit();
|
||||
|
||||
bool m_audioPresent;
|
||||
audio::format m_audioFormat; //!< Audio format buffer
|
||||
uint32_t m_audioSampleRate; //!< sample rate of the buffer
|
||||
std::vector<audio::channel> m_audioMap; //!< Channel map of the buffer
|
||||
bool haveAudio() {
|
||||
return m_audioPresent;
|
||||
}
|
||||
uint32_t audioGetSampleRate() {
|
||||
return m_audioSampleRate;
|
||||
}
|
||||
std::vector<audio::channel> audioGetChannelMap() {
|
||||
return m_audioMap;
|
||||
}
|
||||
audio::format audioGetFormat() {
|
||||
return m_audioFormat;
|
||||
}
|
||||
void seek(const echrono::Duration& _time) {
|
||||
m_seek = _time;
|
||||
}
|
||||
void flushBuffer();
|
||||
};
|
||||
}
|
||||
namespace appl {
|
||||
namespace widget {
|
||||
class VideoDisplay : public ewol::Widget {
|
||||
@ -146,7 +26,7 @@ namespace appl {
|
||||
esignal::Signal<echrono::Duration> signalPosition; //!< signal the current duration of the video duration
|
||||
private:
|
||||
mat4 m_matrixApply;
|
||||
appl::Decoder m_decoder;
|
||||
appl::MediaDecoder m_decoder;
|
||||
ivec2 m_videoSize;
|
||||
ivec2 m_imageSize;
|
||||
echrono::Duration m_LastResetCounter;
|
||||
|
@ -32,6 +32,7 @@ def configure(target, my_module):
|
||||
'appl/debug.cpp',
|
||||
'appl/Main.cpp',
|
||||
'appl/Windows.cpp',
|
||||
'appl/MediaDecoder.cpp',
|
||||
'appl/widget/VideoPlayer.cpp',
|
||||
])
|
||||
my_module.add_depend([
|
||||
|
Loading…
Reference in New Issue
Block a user