Как получить порядковый номер AVFrame после вызова av_seek_frame?
Мне нужно сделать такую логику которая будет читать фреймы с каким то шагом (допустим 20), то есть у меня есть файл и я хочу получить из него фреймы 0, 20, 40, 60...
Для этого у меня есть вот такая логика
AVFrame * m_pAVFrame = nullptr;
int firstFrameIdx = 0;
while(true)
{
if(firstFrameIdx > 0)
{
int64_t seekTarget = FrameToPts(m_pAVStream, firstFrameIdx);
nRet = av_seek_frame(m_pAVFormatCtx, m_streamIdx, seekTarget, AVSEEK_FLAG_FRAME);
}
nRet = av_read_frame(m_pAVFormatCtx, m_pAVPkt);
ret = avcodec_send_packet(m_pAVCodecCtx, m_pAVPkt);
ret = avcodec_receive_frame(m_pAVCodecCtx, m_pAVFrame);
firstFrameIdx+=20;
}
Но проблема в том, что av_seek_frame двигает указатель на Iframe то есть если keyframes в файле каждые 15 фреймов, то в случае когда я делаю av_seek_frame на 20й фрейм то как результат получаю фрейм 15.
Я вижу, что AVFrame есть coded_picture_number который может быть полезным в моем случае я попробовал добавить эти значения в вектор но вижу, что эти значения не имеют смысла
Я ожидал увидеть 0, 15, 30, 45...
Если бы coded_picture_number возвращало бы верное значение (допустим 15), то можно было бы предположить, что это IFrame индекс и пропустить еще 4 фрейма и вот пожалуйста 20й фрейм бери пользуйся)
В общем есть ощущение, что я упускаю тут, что то важное. Кто работал с таким подскажите куда смотреть?
ПРАВКА
Init логика
bool FFmpegDecoder::Init(unsigned char const * pData, int dataSize, int reqId, bool bUseHWAccel, FFmpegDecoderCallback * pCB)
{
Deinit();
// From memory:
if (pData == nullptr || dataSize == 0)
{
printf("FFmpegDecoder::Init FAILED: neither filename nor memory data were given !\n");
return false;
}
m_pIoCtx = std::make_shared<AVIOContextMem>(pData, dataSize);
if (m_pIoCtx->IsValid() == false)
{
printf("FFmpegDecoder::Init FAILED: m_pIoCtx is nullptr !\n");
return false;
}
m_reqId = reqId;
m_bUseHWAccel = bUseHWAccel;
m_pCB = pCB;
m_pData = pData;
m_dataSize = dataSize;
m_bRequestedAbort = false;
m_pAVPkt = av_packet_alloc();
av_init_packet(m_pAVPkt);
m_pAVFrame = av_frame_alloc();
m_pAVFrameRGB = av_frame_alloc();
if (m_bUseHWAccel)
{
m_pSwAVFrameForHw = av_frame_alloc();
}
m_pAVFormatCtx = avformat_alloc_context();
m_pIoCtx->initAVFormatContext(m_pAVFormatCtx);
if (avformat_open_input(&m_pAVFormatCtx, "", nullptr, nullptr) != 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in avformat_open_input\n");
return false;
}
if (avformat_find_stream_info(m_pAVFormatCtx, nullptr) < 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in avformat_find_stream_info\n");
return false;
}
//av_dump_format(ctx_format, 0, "", false);
for (int i = 0; i < (int)m_pAVFormatCtx->nb_streams; i++)
{
if (m_pAVFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
m_streamIdx = i;
m_pAVStream = m_pAVFormatCtx->streams[i];
break;
}
}
if (m_pAVStream == nullptr)
{
printf("FFmpegDecoder::InitFFmpeg: failed to find video stream\n");
return false;
}
m_pAVCodec = avcodec_find_decoder(m_pAVStream->codecpar->codec_id);
if (!m_pAVCodec)
{
printf("FFmpegDecoder::InitFFmpeg: error in avcodec_find_decoder\n");
return false;
}
m_pAVCodecCtx = avcodec_alloc_context3(m_pAVCodec);
if (!m_pAVCodecCtx)
{
printf("FFmpegDecoder::InitFFmpeg: error in avcodec_alloc_context3\n");
return false;
}
if (avcodec_parameters_to_context(m_pAVCodecCtx, m_pAVStream->codecpar) < 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in avcodec_parameters_to_context\n");
return false;
}
if (m_bUseHWAccel)
{
AVHWDeviceType hwDevType = AV_HWDEVICE_TYPE_DXVA2;
g_hwPixFormat = find_fmt_by_hw_type(hwDevType);
m_pAVCodecCtx->get_format = get_hw_format;
av_opt_set_int(m_pAVCodecCtx, "refcounted_frames", 1, 0);
if (av_hwdevice_ctx_create(&m_pBufferRefForHw, hwDevType, NULL, NULL, 0) < 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in av_hwdevice_ctx_create\n");
return false;
}
m_pAVCodecCtx->hw_device_ctx = av_buffer_ref(m_pBufferRefForHw);
}
if (avcodec_open2(m_pAVCodecCtx, m_pAVCodec, nullptr) < 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in avcodec_open2\n");
return false;
}
m_pAVFrameRGB->format = AV_PIX_FMT_BGR24;
m_pAVFrameRGB->width = m_pAVCodecCtx->width;
m_pAVFrameRGB->height = m_pAVCodecCtx->height;
if (av_frame_get_buffer(m_pAVFrameRGB, 32) != 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in av_frame_get_buffer\n");
return false;
}
m_streamRotationDegrees = GetAVStreamRotation(m_pAVStream);
m_estimatedFramesCount = 0;
assert(m_pAVFormatCtx->nb_streams > 0);
if (m_pAVFormatCtx->nb_streams > 0)
{
m_estimatedFramesCount = m_pAVFormatCtx->streams[0]->nb_frames;
}
//InitConvertColorSpace
// Init converter from YUV420p to BGR:
if (m_bUseHWAccel)
{
m_pSwsCtxConvertImg = sws_getContext(m_pAVCodecCtx->width, m_pAVCodecCtx->height, AV_PIX_FMT_NV12, m_pAVCodecCtx->width, m_pAVCodecCtx->height, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
}
else
{
m_pSwsCtxConvertImg = sws_getContext(m_pAVCodecCtx->width, m_pAVCodecCtx->height, m_pAVCodecCtx->pix_fmt, m_pAVCodecCtx->width, m_pAVCodecCtx->height, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
}
if (!m_pSwsCtxConvertImg)
{
printf("FFmpegDecoder::InitFFmpeg: error in sws_getContext\n");
return false;
}
m_bInitOK = true;
return true;
}
Логика декодинга с последними изменениями
void FFmpegDecoder::DecodeWithStep(int step)
{
step = 20;
int currentFramePos = 0;
int number_of_errors = 0;
const int MAX_ERROR_NUM = 10;
int seekPos = 0;
while (true)
{
if (step > 1)
{
seekPos = currentFramePos + step;
int64_t seekTarget = FrameToPts(m_pAVStream, seekPos);
if (av_seek_frame(m_pAVFormatCtx, m_streamIdx, seekTarget, AVSEEK_FLAG_FRAME) < 0)
{
number_of_errors++;
}
else
{
m_is_seeked = true;
}
}
while (true)
{
if (av_read_frame(m_pAVFormatCtx, m_pAVPkt) == 0)
{
if (m_pAVPkt->stream_index == m_streamIdx) //to make sure that I dont get packets from other streams
{
if (m_is_seeked)
{
avcodec_flush_buffers(m_pAVCodecCtx);
m_is_seeked = false;
}
if (avcodec_send_packet(m_pAVCodecCtx, m_pAVPkt) == 0)
{
int ret = 0;
while (ret >= 0)
{
ret = avcodec_receive_frame(m_pAVCodecCtx, m_pAVFrame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
av_frame_unref(m_pAVFrame);
break;
}
currentFramePos = m_pAVFrame->display_picture_number; //In order to get position of currect frame (seek move poiter to the key frame)
if (currentFramePos < seekPos) //Some frames need to be skiped in order to reach needed frame
{
printf("----- SKIP : cur position (%d) \n", currentFramePos);
av_frame_unref(m_pAVFrame);
continue;
}
ProcessFrame(m_pAVFrame); //needed frame was processed
av_frame_unref(m_pAVFrame);
printf("----- cur position (%d) \n", currentFramePos);
break;
}
}
av_packet_unref(m_pAVPkt);
}
else
{
av_packet_unref(m_pAVPkt); //we got a frame from the wrong stream
}
}
else
{
number_of_errors++;
}
if (number_of_errors == MAX_ERROR_NUM)
{
printf("EXIT1\n");
break;
}
}
if (number_of_errors == MAX_ERROR_NUM)
{
printf("EXIT2\n");
break;
}
}
}
Ответы (1 шт):
В итоге вот так получилось решить
void GetFrame(int index) // Zero-based frame index
{
int ret;
double avgFrameDuration = 10000000 / av_q2d(m_pAVFormatCtx->streams[m_streamIdx]->avg_frame_rate);
double m_streamTimebase = av_q2d(m_pAVStream->time_base) * 1000.0 * 10000.0; // Convert timebase to ticks so we can easily convert stream's timestamps to ticks;
long startTime = m_pAVStream->start_time != AV_NOPTS_VALUE ? (long)(m_pAVStream->start_time * m_streamTimebase) : 0; // We will need this when we seek (adding it to seek timestamp);
long frameTimestamp = (long)(index * avgFrameDuration); // Calculation of FrameX timestamp (based on fps/avgFrameDuration)
printf("Searching for : %ld\n", frameTimestamp);
// Seeking at frameTimestamp or previous I/Key frame and flushing codec
ret = av_seek_frame(m_pAVFormatCtx, -1, (startTime + frameTimestamp) / 10, AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD);
avcodec_flush_buffers(m_pAVCodecCtx);
if (ret < 0) return; // handle seek error
while (true)
{
// Demux Packet
ret = av_read_frame(m_pAVFormatCtx, m_pAVPkt);
if (ret != 0) break; // handle EOF/error
if (m_pAVPkt->stream_index != m_streamIdx) { av_packet_unref(m_pAVPkt); continue; } // Exclude other streams
ret = avcodec_send_packet(m_pAVCodecCtx, m_pAVPkt); // Send Packet for decoding
av_packet_unref(m_pAVPkt);
if (ret != 0) break; // handle EOF/error
while (true)
{
// Receive all available frames for the decoder
ret = avcodec_receive_frame(m_pAVCodecCtx, m_pAVFrame);
if (ret != 0) { av_frame_unref(m_pAVFrame); break; }
// Get frame pts (prefer best_effort_timestamp)
long curPts = (long)(m_pAVFrame->best_effort_timestamp == AV_NOPTS_VALUE ? m_pAVFrame->pts : m_pAVFrame->best_effort_timestamp);
if (curPts == AV_NOPTS_VALUE) { { av_frame_unref(m_pAVFrame); continue; } }
// Skip frames before our actual requested frame
printf("Skip pts: %ld\n", curPts);
if ((long)(curPts * m_streamTimebase) / 10000 < frameTimestamp / 10000) { av_frame_unref(m_pAVFrame); continue; }
//needed frame here to process
printf("Found pts: %ld\n", curPts);
av_frame_unref(m_pAVFrame);
return;
}
}
}
