I am demuxing a m3u8 with libav and getting packets to decode them + play, the decoder is mediacodec with a surface set for direct rendering. This works so far but the video plays too fast, extremely fast; tried to play with pts/dts I sent to decoder without succes at the minute.
I saw other implementations that are doing this but they aren't using direct rendering with mediacodec then they're maintaing like a queue of already decoded images to paint. As my implementation is intended to run on a android without too much resources I would need to save as much processing as I can then direct rendering is a must.
Here I put a sample code of what I did:
int MediaParserFFmpeg::init(const std::string &filePath)
{
m_formatCtx = avformat_alloc_context();
int ret = 0;
ret = avformat_open_input(&m_formatCtx, m_mediaPath.c_str(), iFormat, nullptr);
if (ret < 0)
{
return MEDIA_ERROR_INIT_FAILED;
}
ret = avformat_find_stream_info(m_formatCtx, nullptr);
if (ret < 0)
{
return MEDIA_ERROR_INIT_FAILED;
}
m_duration = m_formatCtx->duration;
const AVCodec *vcodec = nullptr;
m_vStreamIdx = av_find_best_stream(m_formatCtx,
AVMEDIA_TYPE_VIDEO,
-1,
-1,
&vcodec,
0);
AVCodecParameters *codecParam = m_formatCtx->streams[m_vStreamIdx]->codecpar;
m_width = codecParam->width;
m_height = codecParam->height;
m_vCodec = codecParam->codec_id;
AVRational fpsRatio = m_formatCtx->streams[m_vStreamIdx]->avg_frame_rate;
m_frameRate = fpsRatio.num / (float)fpsRatio.den;
if (codecParam->codec_id == AV_CODEC_ID_H264)
{
// if stored in ANNEXB type convert to NALU
m_isNal = codecParam->extradata_size > 0 && *(codecParam->extradata) == 1;
if (m_isNal)
{
const AVBitStreamFilter *bsfFilter = av_bsf_get_by_name("h264_mp4toannexb");
if (!bsfFilter)
{
return MEDIA_ERROR_INIT_FAILED;
}
av_bsf_alloc(bsfFilter, &m_bsfcContext);
m_bsfcContext->par_in = codecParam;
av_bsf_init(m_bsfcContext);
m_bsfPacket = av_packet_alloc();
}
}
m_packet = av_packet_alloc();
}
int MediaParserFFmpeg::getNextFrame(MediaType *mediaType, AVEncodedFrame **outFrame)
{
int ret = 0;
ret = av_read_frame(m_formatCtx, m_packet);
if (ret < 0 && ret != AVERROR_EOF)
{
av_packet_unref(m_packet);
return MEDIA_ERROR_PARSER_PARSE_FAILED;
}
if (ret == AVERROR_EOF)
{
av_packet_unref(m_packet);
return MEDIA_INFO_PARSER_END_OF_STREAM;
}
if (m_packet->data && m_packet->size)
{
if (m_vCodec == VIDEOCODEC_H264 && m_isNal)
{
int res = av_bsf_send_packet(m_bsfcContext, m_packet);
if (res != 0)
{
MEDIA_LOG() << "ZError:" << "Error adding NAL to H264 stream";
}
if (res == 0)
{
*outFrame = new EncodedVideoFrame(m_bsfPacket->data,
m_bsfPacket->size,
getTimeStamp(m_bsfPacket, m_formatCtx->streams[m_vStreamIdx]),
m_width,
m_height,
true);
*mediaType = MEDIA_TYPE_VIDEO;
}
av_packet_unref(m_bsfPacket);
res = av_bsf_receive_packet(m_bsfcContext, m_bsfPacket);
}
}
av_packet_unref(m_packet);
return MEDIA_SUCCESS;
}
int64_t MediaParserFFmpeg::getTimeStamp(AVPacket *packet, AVStream *stream)
{
if (packet->pts != (qint64)AV_NOPTS_VALUE)
{
return (1000000 * (packet->pts * ((float)stream->time_base.num / stream->time_base.den)));
}
else if (packet->dts != (qint64)AV_NOPTS_VALUE)
{
return (1000000 * (packet->dts * ((float)stream->time_base.num / stream->time_base.den)));
}
else
{
return 0;
}
}
Then I have a thread that's calling getNextFrame() getting a new EncodedVideoFrame and pushing it to MediaCodec decoder in this way:
AMediaCodec_queueInputBuffer(m_mediaCodec, index, 0, encodedFrame->m_frameSize, encodedFrame->m_timeStamp, 0);
As I said, it works but produces a video that got played extremely fast; already tried to play with pts and dts without luck ( not sure if I was doing something wrong there anyway ).
Any thoughs on how to solve this ?
Thanks to this guy + a guy in my office (kudos my friends) I got the solution, as they pointed, you need to create a wait based on system clock or based in audio clock to wait the duration of each frame; as otherwise it would play as faster as he is able.
Here you go the code to solve this, based on system clock.
An that must be called before calling AMediaCodec_releaseOutputBuffer