Пример #1
0
// position pointer in file, position in second
AVFrame *VideoFFmpeg::grabFrame(long position)
{
	AVPacket packet;
	int frameFinished;
	int posFound = 1;
	bool frameLoaded = false;
	int64_t targetTs = 0;
	CacheFrame *frame;
	int64_t dts = 0;

	if (m_cacheStarted)
	{
		// when cache is active, we must not read the file directly
		do {
			pthread_mutex_lock(&m_cacheMutex);
			frame = (CacheFrame *)m_frameCacheBase.first;
			pthread_mutex_unlock(&m_cacheMutex);
			// no need to remove the frame from the queue: the cache thread does not touch the head, only the tail
			if (frame == NULL)
			{
				// no frame in cache, in case of file it is an abnormal situation
				if (m_isFile)
				{
					// go back to no threaded reading
					stopCache();
					break;
				}
				return NULL;
			}
			if (frame->framePosition == -1)
			{
				// this frame mark the end of the file (only used for file)
				// leave in cache to make sure we don't miss it
				m_eof = true;
				return NULL;
			}
			// for streaming, always return the next frame,
			// that's what grabFrame does in non cache mode anyway.
			if (m_isStreaming || frame->framePosition == position)
			{
				return frame->frame;
			}
			// for cam, skip old frames to keep image realtime.
			// There should be no risk of clock drift since it all happens on the same CPU
			if (frame->framePosition > position)
			{
				// this can happen after rewind if the seek didn't find the first frame
				// the frame in the buffer is ahead of time, just leave it there
				return NULL;
			}
			// this frame is not useful, release it
			pthread_mutex_lock(&m_cacheMutex);
			BLI_remlink(&m_frameCacheBase, frame);
			BLI_addtail(&m_frameCacheFree, frame);
			pthread_mutex_unlock(&m_cacheMutex);
		} while (true);
	}
	double timeBase = av_q2d(m_formatCtx->streams[m_videoStream]->time_base);
	int64_t startTs = m_formatCtx->streams[m_videoStream]->start_time;
	if (startTs == AV_NOPTS_VALUE)
		startTs = 0;

	// come here when there is no cache or cache has been stopped
	// locate the frame, by seeking if necessary (seeking is only possible for files)
	if (m_isFile)
	{
		// first check if the position that we are looking for is in the preseek range
		// if so, just read the frame until we get there
		if (position > m_curPosition + 1
			&& m_preseek
			&& position - (m_curPosition + 1) < m_preseek)
		{
			while (av_read_frame(m_formatCtx, &packet)>=0)
			{
				if (packet.stream_index == m_videoStream)
				{
					avcodec_decode_video2(
						m_codecCtx,
						m_frame, &frameFinished,
						&packet);
					if (frameFinished)
					{
						m_curPosition = (long)((packet.dts-startTs) * (m_baseFrameRate*timeBase) + 0.5);
					}
				}
				av_free_packet(&packet);
				if (position == m_curPosition+1)
					break;
			}
		}
		// if the position is not in preseek, do a direct jump
		if (position != m_curPosition + 1)
		{
			int64_t pos = (int64_t)((position - m_preseek) / (m_baseFrameRate*timeBase));

			if (pos < 0)
				pos = 0;

			pos += startTs;

			if (position <= m_curPosition || !m_eof)
			{
#if 0
				// Tried to make this work but couldn't: seeking on byte is ignored by the
				// format plugin and it will generally continue to read from last timestamp.
				// Too bad because frame seek is not always able to get the first frame
				// of the file.
				if (position <= m_preseek)
				{
					// we can safely go the beginning of the file
					if (av_seek_frame(m_formatCtx, m_videoStream, 0, AVSEEK_FLAG_BYTE) >= 0)
					{
						// binary seek does not reset the timestamp, must do it now
						av_update_cur_dts(m_formatCtx, m_formatCtx->streams[m_videoStream], startTs);
						m_curPosition = 0;
					}
				}
				else
#endif
				{
					// current position is now lost, guess a value.
					if (av_seek_frame(m_formatCtx, m_videoStream, pos, AVSEEK_FLAG_BACKWARD) >= 0)
					{
						// current position is now lost, guess a value.
						// It's not important because it will be set at this end of this function
						m_curPosition = position - m_preseek - 1;
					}
				}
			}
			// this is the timestamp of the frame we're looking for
			targetTs = (int64_t)(position / (m_baseFrameRate * timeBase)) + startTs;

			posFound = 0;
			avcodec_flush_buffers(m_codecCtx);
		}
	} else if (m_isThreaded)
	{
		// cache is not started but threading is possible
		// better not read the stream => make take some time, better start caching
		if (startCache())
			return NULL;
		// Abnormal!!! could not start cache, fall back on direct read
		m_isThreaded = false;
	}

	// find the correct frame, in case of streaming and no cache, it means just
	// return the next frame. This is not quite correct, may need more work
	while (av_read_frame(m_formatCtx, &packet) >= 0)
	{
		if (packet.stream_index == m_videoStream)
		{
			AVFrame *input = m_frame;
			short counter = 0;

			/* If m_isImage, while the data is not read properly (png, tiffs, etc formats may need several pass), else don't need while loop*/
			do {
				avcodec_decode_video2(m_codecCtx, m_frame, &frameFinished, &packet);
				counter++;
			} while ((input->data[0] == 0 && input->data[1] == 0 && input->data[2] == 0 && input->data[3] == 0) && counter < 10 && m_isImage);

			// remember dts to compute exact frame number
			dts = packet.dts;
			if (frameFinished && !posFound)
			{
				if (dts >= targetTs)
				{
					posFound = 1;
				}
			}

			if (frameFinished && posFound == 1)
			{
				AVFrame * input = m_frame;

				/* This means the data wasnt read properly,
				 * this check stops crashing */
				if (   input->data[0]==0 && input->data[1]==0
					&& input->data[2]==0 && input->data[3]==0)
				{
					av_free_packet(&packet);
					break;
				}

				if (m_deinterlace)
				{
					if (avpicture_deinterlace(
						(AVPicture*) m_frameDeinterlaced,
						(const AVPicture*) m_frame,
						m_codecCtx->pix_fmt,
						m_codecCtx->width,
						m_codecCtx->height) >= 0)
					{
						input = m_frameDeinterlaced;
					}
				}
				// convert to RGB24
				sws_scale(m_imgConvertCtx,
					input->data,
					input->linesize,
					0,
					m_codecCtx->height,
					m_frameRGB->data,
					m_frameRGB->linesize);
				av_free_packet(&packet);
				frameLoaded = true;
				break;
			}
		}
		av_free_packet(&packet);
	}
	m_eof = m_isFile && !frameLoaded;
	if (frameLoaded)
	{
		m_curPosition = (long)((dts-startTs) * (m_baseFrameRate*timeBase) + 0.5);
		if (m_isThreaded)
		{
			// normal case for file: first locate, then start cache
			if (!startCache())
			{
				// Abnormal!! could not start cache, return to non-cache mode
				m_isThreaded = false;
			}
		}
		return m_frameRGB;
	}
	return NULL;
}
Пример #2
0
static ImBuf *ffmpeg_fetchibuf(struct anim *anim, int position,
                               IMB_Timecode_Type tc)
{
	int64_t pts_to_search = 0;
	double frame_rate;
	double pts_time_base;
	long long st_time; 
	struct anim_index *tc_index = 0;
	AVStream *v_st;
	int new_frame_index = 0; /* To quiet gcc barking... */
	int old_frame_index = 0; /* To quiet gcc barking... */

	if (anim == 0) return (0);

	av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: pos=%d\n", position);

	if (tc != IMB_TC_NONE) {
		tc_index = IMB_anim_open_index(anim, tc);
	}

	v_st = anim->pFormatCtx->streams[anim->videoStream];

	frame_rate = av_q2d(v_st->r_frame_rate);

	st_time = anim->pFormatCtx->start_time;
	pts_time_base = av_q2d(v_st->time_base);

	if (tc_index) {
		new_frame_index = IMB_indexer_get_frame_index(
		        tc_index, position);
		old_frame_index = IMB_indexer_get_frame_index(
		        tc_index, anim->curposition);
		pts_to_search = IMB_indexer_get_pts(
		        tc_index, new_frame_index);
	}
	else {
		pts_to_search = (long long) 
		                floor(((double) position) /
		                      pts_time_base / frame_rate + 0.5);

		if (st_time != AV_NOPTS_VALUE) {
			pts_to_search += st_time / pts_time_base / AV_TIME_BASE;
		}
	}

	av_log(anim->pFormatCtx, AV_LOG_DEBUG, 
	       "FETCH: looking for PTS=%lld "
	       "(pts_timebase=%g, frame_rate=%g, st_time=%lld)\n", 
	       (long long int)pts_to_search, pts_time_base, frame_rate, st_time);

	if (anim->last_frame && 
	    anim->last_pts <= pts_to_search && anim->next_pts > pts_to_search)
	{
		av_log(anim->pFormatCtx, AV_LOG_DEBUG, 
		       "FETCH: frame repeat: last: %lld next: %lld\n",
		       (long long int)anim->last_pts, 
		       (long long int)anim->next_pts);
		IMB_refImBuf(anim->last_frame);
		anim->curposition = position;
		return anim->last_frame;
	}
	 
	if (position > anim->curposition + 1 &&
	    anim->preseek &&
	    !tc_index &&
	    position - (anim->curposition + 1) < anim->preseek)
	{
		av_log(anim->pFormatCtx, AV_LOG_DEBUG, 
		       "FETCH: within preseek interval (no index)\n");

		ffmpeg_decode_video_frame_scan(anim, pts_to_search);
	}
	else if (tc_index &&
	         IMB_indexer_can_scan(tc_index, old_frame_index,
	                              new_frame_index))
	{
		av_log(anim->pFormatCtx, AV_LOG_DEBUG, 
		       "FETCH: within preseek interval "
		       "(index tells us)\n");

		ffmpeg_decode_video_frame_scan(anim, pts_to_search);
	}
	else if (position != anim->curposition + 1) {
		long long pos;
		int ret;

		if (tc_index) {
			unsigned long long dts;

			pos = IMB_indexer_get_seek_pos(
			    tc_index, new_frame_index);
			dts = IMB_indexer_get_seek_pos_dts(
			    tc_index, new_frame_index);

			av_log(anim->pFormatCtx, AV_LOG_DEBUG, 
			       "TC INDEX seek pos = %lld\n", pos);
			av_log(anim->pFormatCtx, AV_LOG_DEBUG, 
			       "TC INDEX seek dts = %lld\n", dts);

			if (ffmpeg_seek_by_byte(anim->pFormatCtx)) {
				av_log(anim->pFormatCtx, AV_LOG_DEBUG, 
				       "... using BYTE pos\n");

				ret = av_seek_frame(anim->pFormatCtx, 
				                    -1,
				                    pos, AVSEEK_FLAG_BYTE);
				av_update_cur_dts(anim->pFormatCtx, v_st, dts);
			}
			else {
				av_log(anim->pFormatCtx, AV_LOG_DEBUG, 
				       "... using DTS pos\n");
				ret = av_seek_frame(anim->pFormatCtx, 
				                    anim->videoStream,
				                    dts, AVSEEK_FLAG_BACKWARD);
			}
		}
		else {
			pos = (long long) (position - anim->preseek) *
			      AV_TIME_BASE / frame_rate;

			av_log(anim->pFormatCtx, AV_LOG_DEBUG, 
			       "NO INDEX seek pos = %lld, st_time = %lld\n", 
			       pos, (st_time != AV_NOPTS_VALUE) ? st_time : 0);

			if (pos < 0) {
				pos = 0;
			}
		
			if (st_time != AV_NOPTS_VALUE) {
				pos += st_time;
			}

			av_log(anim->pFormatCtx, AV_LOG_DEBUG, 
			       "NO INDEX final seek pos = %lld\n", pos);

			ret = av_seek_frame(anim->pFormatCtx, -1, 
			                    pos, AVSEEK_FLAG_BACKWARD);
		}

		if (ret < 0) {
			av_log(anim->pFormatCtx, AV_LOG_ERROR,
			       "FETCH: "
			       "error while seeking to DTS = %lld "
			       "(frameno = %d, PTS = %lld): errcode = %d\n",
			       pos, position, (long long int)pts_to_search, ret);
		}

		avcodec_flush_buffers(anim->pCodecCtx);

		anim->next_pts = -1;

		if (anim->next_packet.stream_index == anim->videoStream) {
			av_free_packet(&anim->next_packet);
			anim->next_packet.stream_index = -1;
		}

		/* memset(anim->pFrame, ...) ?? */

		if (ret >= 0) {
			ffmpeg_decode_video_frame_scan(anim, pts_to_search);
		}
	}
	else if (position == 0 && anim->curposition == -1) {
		/* first frame without seeking special case... */
		ffmpeg_decode_video_frame(anim);
	}
	else {
		av_log(anim->pFormatCtx, AV_LOG_DEBUG, 
		       "FETCH: no seek necessary, just continue...\n");
	}

	IMB_freeImBuf(anim->last_frame);
	anim->last_frame = IMB_allocImBuf(anim->x, anim->y, 32, IB_rect);
	anim->last_frame->rect_colorspace = colormanage_colorspace_get_named(anim->colorspace);

	ffmpeg_postprocess(anim);

	anim->last_pts = anim->next_pts;
	
	ffmpeg_decode_video_frame(anim);
	
	anim->curposition = position;
	
	IMB_refImBuf(anim->last_frame);

	return anim->last_frame;
}