2016-08-27 19:10:27 +00:00
|
|
|
#if defined(CONF_VIDEORECORDER)
|
|
|
|
|
2016-08-27 15:51:23 +00:00
|
|
|
#include <engine/storage.h>
|
|
|
|
#include <engine/console.h>
|
2016-08-30 23:39:59 +00:00
|
|
|
#include <engine/shared/config.h>
|
2016-08-27 15:51:23 +00:00
|
|
|
|
|
|
|
#include "video.h"
|
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
// This code is mostly stolen from https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/muxing.c
|
|
|
|
|
2016-08-27 19:10:27 +00:00
|
|
|
#define STREAM_PIX_FMT AV_PIX_FMT_YUV420P /* default pix_fmt */
|
|
|
|
|
2019-09-27 03:06:02 +00:00
|
|
|
CVideo::CVideo(CGraphics_Threaded* pGraphics, IStorage* pStorage, IConsole *pConsole, int width, int height, const char *name) :
|
2016-08-30 23:39:59 +00:00
|
|
|
m_pGraphics(pGraphics),
|
2016-08-27 15:51:23 +00:00
|
|
|
m_pStorage(pStorage),
|
2016-08-27 19:10:27 +00:00
|
|
|
m_pConsole(pConsole),
|
|
|
|
m_VideoStream(),
|
|
|
|
m_AudioStream()
|
2016-08-27 15:51:23 +00:00
|
|
|
{
|
|
|
|
m_pPixels = 0;
|
|
|
|
|
2016-08-27 19:10:27 +00:00
|
|
|
m_pFormatContext = 0;
|
|
|
|
m_pFormat = 0;
|
2016-08-27 15:51:23 +00:00
|
|
|
m_pRGB = 0;
|
2016-08-27 19:10:27 +00:00
|
|
|
m_pOptDict = 0;
|
|
|
|
|
|
|
|
m_VideoCodec = 0;
|
|
|
|
m_AudioCodec = 0;
|
2016-08-27 15:51:23 +00:00
|
|
|
|
|
|
|
m_Width = width;
|
|
|
|
m_Height = height;
|
2019-09-27 03:06:02 +00:00
|
|
|
str_copy(m_Name, name, sizeof(m_Name));
|
2016-08-27 15:51:23 +00:00
|
|
|
|
2016-08-31 16:07:27 +00:00
|
|
|
m_FPS = g_Config.m_ClVideoRecorderFPS;
|
|
|
|
|
2016-08-27 15:51:23 +00:00
|
|
|
m_Recording = false;
|
2016-08-30 23:39:59 +00:00
|
|
|
m_Started = false;
|
|
|
|
m_ProcessingVideoFrame = false;
|
|
|
|
m_ProcessingAudioFrame = false;
|
|
|
|
|
|
|
|
m_NextFrame = false;
|
2020-01-03 19:37:27 +00:00
|
|
|
m_NextaFrame = false;
|
2016-08-31 20:04:40 +00:00
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
// TODO:
|
2019-10-17 05:38:00 +00:00
|
|
|
m_HasAudio = g_Config.m_ClVideoSndEnable;
|
2016-08-27 15:51:23 +00:00
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
m_SndBufferSize = g_Config.m_SndBufferSize;
|
|
|
|
|
|
|
|
dbg_assert(ms_pCurrentVideo == 0, "ms_pCurrentVideo is NOT set to NULL while creating a new Video.");
|
|
|
|
|
2016-08-31 16:07:27 +00:00
|
|
|
ms_TickTime = time_freq() / m_FPS;
|
2016-08-27 15:51:23 +00:00
|
|
|
ms_pCurrentVideo = this;
|
|
|
|
}
|
|
|
|
|
|
|
|
CVideo::~CVideo()
|
|
|
|
{
|
|
|
|
ms_pCurrentVideo = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CVideo::start()
|
|
|
|
{
|
2016-08-31 16:07:27 +00:00
|
|
|
char aDate[20];
|
|
|
|
str_timestamp(aDate, sizeof(aDate));
|
2019-09-27 03:06:02 +00:00
|
|
|
char aBuf[256];
|
2019-09-27 03:07:50 +00:00
|
|
|
if (strlen(m_Name) != 0)
|
2019-09-27 03:06:02 +00:00
|
|
|
str_format(aBuf, sizeof(aBuf), "videos/%s", m_Name);
|
|
|
|
else
|
|
|
|
str_format(aBuf, sizeof(aBuf), "videos/%s.mp4", aDate);
|
2016-08-31 16:07:27 +00:00
|
|
|
|
2016-08-27 19:10:27 +00:00
|
|
|
char aWholePath[1024];
|
2016-08-31 16:07:27 +00:00
|
|
|
IOHANDLE File = m_pStorage->OpenFile(aBuf, IOFLAG_WRITE, IStorage::TYPE_SAVE, aWholePath, sizeof(aWholePath));
|
2016-08-27 19:10:27 +00:00
|
|
|
|
|
|
|
if(File)
|
|
|
|
{
|
|
|
|
io_close(File);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dbg_msg("video_recorder", "Failed to open file for recoding video.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
avformat_alloc_output_context2(&m_pFormatContext, 0, "mp4", aWholePath);
|
|
|
|
|
|
|
|
if (!m_pFormatContext)
|
|
|
|
{
|
|
|
|
dbg_msg("video_recorder", "Failed to create formatcontext for recoding video.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_pFormat = m_pFormatContext->oformat;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Add the audio and video streams using the default format codecs
|
|
|
|
* and initialize the codecs. */
|
|
|
|
if (m_pFormat->video_codec != AV_CODEC_ID_NONE)
|
|
|
|
{
|
|
|
|
add_stream(&m_VideoStream, m_pFormatContext, &m_VideoCodec, m_pFormat->video_codec);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dbg_msg("video_recorder", "Failed to add VideoStream for recoding video.");
|
|
|
|
}
|
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
if (m_HasAudio && m_pFormat->audio_codec != AV_CODEC_ID_NONE)
|
2016-08-27 19:10:27 +00:00
|
|
|
{
|
|
|
|
add_stream(&m_AudioStream, m_pFormatContext, &m_AudioCodec, m_pFormat->audio_codec);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dbg_msg("video_recorder", "No audio.");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now that all the parameters are set, we can open the audio and
|
|
|
|
* video codecs and allocate the necessary encode buffers. */
|
|
|
|
open_video();
|
|
|
|
|
|
|
|
if (m_HasAudio)
|
|
|
|
open_audio();
|
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
// TODO: remove/comment:
|
2016-08-27 19:10:27 +00:00
|
|
|
av_dump_format(m_pFormatContext, 0, aWholePath, 1);
|
|
|
|
|
|
|
|
/* open the output file, if needed */
|
|
|
|
if (!(m_pFormat->flags & AVFMT_NOFILE))
|
|
|
|
{
|
|
|
|
int ret = avio_open(&m_pFormatContext->pb, aWholePath, AVIO_FLAG_WRITE);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
2016-08-31 12:09:06 +00:00
|
|
|
char aBuf[AV_ERROR_MAX_STRING_SIZE];
|
|
|
|
av_strerror(ret, aBuf, sizeof(aBuf));
|
|
|
|
dbg_msg("video_recorder", "Could not open '%s': %s", aWholePath, aBuf);
|
2016-08-27 19:10:27 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Write the stream header, if any. */
|
|
|
|
int ret = avformat_write_header(m_pFormatContext, &m_pOptDict);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
2016-08-31 12:09:06 +00:00
|
|
|
char aBuf[AV_ERROR_MAX_STRING_SIZE];
|
|
|
|
av_strerror(ret, aBuf, sizeof(aBuf));
|
|
|
|
dbg_msg("video_recorder", "Error occurred when opening output file: %s", aBuf);
|
2016-08-27 19:10:27 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-08-27 15:51:23 +00:00
|
|
|
m_Recording = true;
|
2016-08-30 23:39:59 +00:00
|
|
|
m_Started = true;
|
|
|
|
ms_Time = time_get();
|
2019-10-31 12:02:41 +00:00
|
|
|
m_vframe = 0;
|
2016-08-27 15:51:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CVideo::stop()
|
|
|
|
{
|
|
|
|
m_Recording = false;
|
2016-08-30 23:39:59 +00:00
|
|
|
|
|
|
|
while (m_ProcessingVideoFrame || m_ProcessingAudioFrame)
|
|
|
|
thread_sleep(10);
|
|
|
|
|
|
|
|
finish_frames(&m_VideoStream);
|
|
|
|
|
|
|
|
if (m_HasAudio)
|
|
|
|
finish_frames(&m_AudioStream);
|
|
|
|
|
|
|
|
av_write_trailer(m_pFormatContext);
|
2016-08-27 19:10:27 +00:00
|
|
|
|
|
|
|
close_stream(&m_VideoStream);
|
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
if (m_HasAudio)
|
|
|
|
close_stream(&m_AudioStream);
|
2019-10-31 14:01:12 +00:00
|
|
|
//fclose(m_dbgfile);
|
2016-08-30 23:39:59 +00:00
|
|
|
|
2016-08-27 19:10:27 +00:00
|
|
|
if (!(m_pFormat->flags & AVFMT_NOFILE))
|
|
|
|
avio_closep(&m_pFormatContext->pb);
|
|
|
|
|
|
|
|
if (m_pFormatContext)
|
|
|
|
avformat_free_context(m_pFormatContext);
|
|
|
|
|
2016-08-27 15:51:23 +00:00
|
|
|
if (m_pRGB)
|
|
|
|
free(m_pRGB);
|
|
|
|
|
|
|
|
if (m_pPixels)
|
|
|
|
free(m_pPixels);
|
2019-11-02 08:09:00 +00:00
|
|
|
|
|
|
|
if (ms_pCurrentVideo)
|
|
|
|
delete ms_pCurrentVideo;
|
2016-08-27 15:51:23 +00:00
|
|
|
}
|
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
void CVideo::nextVideoFrame_thread()
|
2016-08-27 15:51:23 +00:00
|
|
|
{
|
2019-11-03 03:20:24 +00:00
|
|
|
if (m_NextFrame && m_Recording)
|
2016-08-27 15:51:23 +00:00
|
|
|
{
|
2019-11-02 08:09:00 +00:00
|
|
|
m_ProcessingVideoFrame = true;
|
2016-08-30 23:39:59 +00:00
|
|
|
// #ifdef CONF_PLATFORM_MACOSX
|
|
|
|
// CAutoreleasePool AutoreleasePool;
|
|
|
|
// #endif
|
2019-10-31 07:36:38 +00:00
|
|
|
m_vseq += 1;
|
|
|
|
if(m_vseq >= 2)
|
2019-09-28 13:22:25 +00:00
|
|
|
{
|
|
|
|
m_VideoStream.frame->pts = m_VideoStream.enc->frame_number;
|
|
|
|
dbg_msg("video_recorder", "vframe: %d", m_VideoStream.enc->frame_number);
|
2016-08-30 23:39:59 +00:00
|
|
|
|
2019-09-28 13:22:25 +00:00
|
|
|
read_rgb_from_gl();
|
|
|
|
fill_video_frame();
|
|
|
|
write_frame(&m_VideoStream);
|
|
|
|
}
|
2016-08-30 23:39:59 +00:00
|
|
|
|
|
|
|
m_ProcessingVideoFrame = false;
|
|
|
|
m_NextFrame = false;
|
|
|
|
// sync_barrier();
|
|
|
|
// m_Semaphore.signal();
|
2016-08-27 15:51:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
void CVideo::nextVideoFrame()
|
2016-08-27 15:51:23 +00:00
|
|
|
{
|
2016-08-30 23:39:59 +00:00
|
|
|
if (m_Recording)
|
2016-08-27 15:51:23 +00:00
|
|
|
{
|
2016-08-30 23:39:59 +00:00
|
|
|
// #ifdef CONF_PLATFORM_MACOSX
|
|
|
|
// CAutoreleasePool AutoreleasePool;
|
|
|
|
// #endif
|
2016-08-27 15:51:23 +00:00
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
dbg_msg("video_recorder", "called");
|
2016-08-27 15:51:23 +00:00
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
ms_Time += ms_TickTime;
|
|
|
|
ms_LocalTime = (ms_Time-ms_LocalStartTime)/(float)time_freq();
|
|
|
|
m_NextFrame = true;
|
2019-10-31 12:02:41 +00:00
|
|
|
m_vframe += 1;
|
2016-08-27 15:51:23 +00:00
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
// m_pGraphics->KickCommandBuffer();
|
|
|
|
//thread_sleep(500);
|
2016-08-27 19:10:27 +00:00
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
// m_Semaphore.wait();
|
2016-08-27 15:51:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-02 08:09:00 +00:00
|
|
|
void CVideo::nextAudioFrame_timeline()
|
2016-08-27 15:51:23 +00:00
|
|
|
{
|
2019-11-03 03:20:24 +00:00
|
|
|
if (m_Recording && m_HasAudio && !m_NextaFrame)
|
2016-08-30 23:39:59 +00:00
|
|
|
{
|
2019-11-02 08:09:00 +00:00
|
|
|
if ((double)(m_vframe/m_FPS) >= m_AudioStream.enc->frame_number*m_AudioStream.enc->frame_size/m_AudioStream.enc->sample_rate)
|
|
|
|
{
|
|
|
|
m_NextaFrame = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CVideo::nextAudioFrame(void (*Mix)(short *pFinalOut, unsigned Frames))
|
|
|
|
{
|
2020-01-01 15:56:48 +00:00
|
|
|
if (m_NextaFrame && m_Recording && m_HasAudio)
|
2019-11-02 08:09:00 +00:00
|
|
|
{
|
|
|
|
m_ProcessingAudioFrame = true;
|
2019-10-31 12:02:41 +00:00
|
|
|
//dbg_msg("video recorder", "video_frame: %lf", (double)(m_vframe/m_FPS));
|
2019-10-31 14:01:12 +00:00
|
|
|
//if((double)(m_vframe/m_FPS) < m_AudioStream.enc->frame_number*m_AudioStream.enc->frame_size/m_AudioStream.enc->sample_rate)
|
|
|
|
//return;
|
2019-11-02 08:09:00 +00:00
|
|
|
Mix(m_aBuffer, ALEN);
|
2019-10-31 07:36:38 +00:00
|
|
|
//m_AudioStream.frame->pts = m_AudioStream.enc->frame_number;
|
2019-10-26 11:54:25 +00:00
|
|
|
dbg_msg("video_recorder", "aframe: %d", m_AudioStream.enc->frame_number);
|
2016-08-27 15:51:23 +00:00
|
|
|
|
2016-08-31 12:09:06 +00:00
|
|
|
// memcpy(m_AudioStream.tmp_frame->data[0], pData, sizeof(int16_t) * m_SndBufferSize * 2);
|
2016-08-30 23:39:59 +00:00
|
|
|
//
|
|
|
|
// for (int i = 0; i < m_SndBufferSize; i++)
|
|
|
|
// {
|
|
|
|
// dbg_msg("video_recorder", "test: %d %d", ((int16_t*)pData)[i*2], ((int16_t*)pData)[i*2 + 1]);
|
|
|
|
// }
|
2016-08-27 15:51:23 +00:00
|
|
|
|
2019-10-31 07:36:38 +00:00
|
|
|
int dst_nb_samples;
|
2016-08-27 19:10:27 +00:00
|
|
|
|
2019-10-31 07:36:38 +00:00
|
|
|
dst_nb_samples = av_rescale_rnd(
|
|
|
|
swr_get_delay(
|
|
|
|
m_AudioStream.swr_ctx,
|
|
|
|
m_AudioStream.enc->sample_rate
|
|
|
|
) + m_AudioStream.tmp_frame->nb_samples * 2,
|
|
|
|
|
|
|
|
m_AudioStream.enc->sample_rate,
|
|
|
|
m_AudioStream.enc->sample_rate, AV_ROUND_UP
|
|
|
|
);
|
2016-08-27 19:10:27 +00:00
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
// dbg_msg("video_recorder", "dst_nb_samples: %d", dst_nb_samples);
|
2019-10-31 07:36:38 +00:00
|
|
|
// fwrite(m_aBuffer, sizeof(short), 2048, m_dbgfile);
|
2016-08-27 15:51:23 +00:00
|
|
|
|
2016-08-31 12:09:06 +00:00
|
|
|
av_samples_fill_arrays(
|
|
|
|
(uint8_t**)m_AudioStream.tmp_frame->data,
|
|
|
|
0, // pointer to linesize (int*)
|
2019-10-29 16:01:25 +00:00
|
|
|
(const uint8_t*)m_aBuffer,
|
2016-08-31 12:09:06 +00:00
|
|
|
2, // channels
|
2019-10-29 16:01:25 +00:00
|
|
|
m_AudioStream.tmp_frame->nb_samples * 2,
|
2019-10-26 11:54:25 +00:00
|
|
|
AV_SAMPLE_FMT_S16,
|
2016-08-31 12:09:06 +00:00
|
|
|
0 // align
|
|
|
|
);
|
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
|
|
|
|
int ret = av_frame_make_writable(m_AudioStream.frame);
|
|
|
|
if (ret < 0)
|
|
|
|
exit(1);
|
|
|
|
|
|
|
|
/* convert to destination format */
|
|
|
|
ret = swr_convert(
|
|
|
|
m_AudioStream.swr_ctx,
|
|
|
|
m_AudioStream.frame->data,
|
2019-10-29 16:01:25 +00:00
|
|
|
m_AudioStream.frame->nb_samples * 2,
|
2016-08-30 23:39:59 +00:00
|
|
|
(const uint8_t **)m_AudioStream.tmp_frame->data,
|
2019-10-29 16:01:25 +00:00
|
|
|
m_AudioStream.tmp_frame->nb_samples * 2
|
2016-08-30 23:39:59 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
if (ret < 0)
|
2016-08-27 15:51:23 +00:00
|
|
|
{
|
2016-08-30 23:39:59 +00:00
|
|
|
dbg_msg("video_recorder", "Error while converting");
|
|
|
|
exit(1);
|
2016-08-27 15:51:23 +00:00
|
|
|
}
|
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
// frame = ost->frame;
|
|
|
|
//
|
2019-10-31 07:36:38 +00:00
|
|
|
m_AudioStream.frame->pts = av_rescale_q(m_AudioStream.samples_count, (AVRational){1, m_AudioStream.enc->sample_rate}, m_AudioStream.enc->time_base);
|
|
|
|
m_AudioStream.samples_count += dst_nb_samples;
|
2016-08-30 23:39:59 +00:00
|
|
|
|
|
|
|
// dbg_msg("video_recorder", "prewrite----");
|
|
|
|
write_frame(&m_AudioStream);
|
|
|
|
|
|
|
|
m_ProcessingAudioFrame = false;
|
2019-11-02 08:09:00 +00:00
|
|
|
m_NextaFrame = false;
|
2016-08-30 23:39:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CVideo::fill_audio_frame()
|
|
|
|
{
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CVideo::fill_video_frame()
|
|
|
|
{
|
|
|
|
const int in_linesize[1] = { 3 * m_VideoStream.enc->width };
|
|
|
|
if (!m_VideoStream.sws_ctx)
|
2016-08-27 15:51:23 +00:00
|
|
|
{
|
2016-08-30 23:39:59 +00:00
|
|
|
m_VideoStream.sws_ctx = sws_getCachedContext(
|
|
|
|
m_VideoStream.sws_ctx,
|
|
|
|
m_VideoStream.enc->width, m_VideoStream.enc->height, AV_PIX_FMT_RGB24,
|
|
|
|
m_VideoStream.enc->width, m_VideoStream.enc->height, AV_PIX_FMT_YUV420P,
|
|
|
|
0, 0, 0, 0
|
|
|
|
);
|
2016-08-27 15:51:23 +00:00
|
|
|
}
|
2016-08-30 23:39:59 +00:00
|
|
|
sws_scale(m_VideoStream.sws_ctx, (const uint8_t * const *)&m_pRGB, in_linesize, 0,
|
|
|
|
m_VideoStream.enc->height, m_VideoStream.frame->data, m_VideoStream.frame->linesize);
|
2016-08-27 15:51:23 +00:00
|
|
|
}
|
|
|
|
|
2016-08-27 19:10:27 +00:00
|
|
|
void CVideo::read_rgb_from_gl()
|
2016-08-27 15:51:23 +00:00
|
|
|
{
|
2019-11-12 13:41:30 +00:00
|
|
|
int i, j, k;
|
|
|
|
size_t cur_gl, cur_rgb, nvals;
|
2016-08-27 15:51:23 +00:00
|
|
|
const size_t format_nchannels = 3;
|
|
|
|
nvals = format_nchannels * m_Width * m_Height;
|
|
|
|
m_pPixels = (uint8_t *)realloc(m_pPixels, nvals * sizeof(GLubyte));
|
|
|
|
m_pRGB = (uint8_t *)realloc(m_pRGB, nvals * sizeof(uint8_t));
|
|
|
|
/* Get RGBA to align to 32 bits instead of just 24 for RGB. May be faster for FFmpeg. */
|
2019-10-01 13:01:23 +00:00
|
|
|
glReadBuffer(GL_FRONT);
|
2016-08-27 15:51:23 +00:00
|
|
|
glReadPixels(0, 0, m_Width, m_Height, GL_RGB, GL_UNSIGNED_BYTE, m_pPixels);
|
|
|
|
for (i = 0; i < m_Height; i++)
|
|
|
|
{
|
|
|
|
for (j = 0; j < m_Width; j++)
|
|
|
|
{
|
|
|
|
cur_gl = format_nchannels * (m_Width * (m_Height - i - 1) + j);
|
|
|
|
cur_rgb = format_nchannels * (m_Width * i + j);
|
2019-11-12 13:41:30 +00:00
|
|
|
for (k = 0; k < (int)format_nchannels; k++)
|
2016-08-27 15:51:23 +00:00
|
|
|
m_pRGB[cur_rgb + k] = m_pPixels[cur_gl + k];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-08-27 19:10:27 +00:00
|
|
|
|
|
|
|
|
|
|
|
AVFrame* CVideo::alloc_picture(enum AVPixelFormat pix_fmt, int width, int height)
|
|
|
|
{
|
|
|
|
AVFrame* picture;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
picture = av_frame_alloc();
|
|
|
|
if (!picture)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
picture->format = pix_fmt;
|
|
|
|
picture->width = width;
|
|
|
|
picture->height = height;
|
|
|
|
|
|
|
|
/* allocate the buffers for the frame data */
|
|
|
|
ret = av_frame_get_buffer(picture, 32);
|
|
|
|
if (ret < 0) {
|
|
|
|
dbg_msg("video_recorder", "Could not allocate frame data.");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return picture;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
AVFrame* CVideo::alloc_audio_frame(enum AVSampleFormat sample_fmt, uint64_t channel_layout, int sample_rate, int nb_samples)
|
|
|
|
{
|
|
|
|
AVFrame *frame = av_frame_alloc();
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!frame) {
|
|
|
|
dbg_msg("video_recorder", "Error allocating an audio frame");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
frame->format = sample_fmt;
|
|
|
|
frame->channel_layout = channel_layout;
|
|
|
|
frame->sample_rate = sample_rate;
|
|
|
|
frame->nb_samples = nb_samples;
|
|
|
|
|
|
|
|
if (nb_samples) {
|
|
|
|
ret = av_frame_get_buffer(frame, 0);
|
|
|
|
if (ret < 0) {
|
|
|
|
dbg_msg("video_recorder", "Error allocating an audio buffer");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CVideo::open_video()
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
AVCodecContext* c = m_VideoStream.enc;
|
|
|
|
AVDictionary* opt = 0;
|
|
|
|
|
|
|
|
av_dict_copy(&opt, m_pOptDict, 0);
|
|
|
|
|
|
|
|
/* open the codec */
|
|
|
|
ret = avcodec_open2(c, m_VideoCodec, &opt);
|
|
|
|
av_dict_free(&opt);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
2016-08-31 12:09:06 +00:00
|
|
|
char aBuf[AV_ERROR_MAX_STRING_SIZE];
|
|
|
|
av_strerror(ret, aBuf, sizeof(aBuf));
|
|
|
|
dbg_msg("video_recorder", "Could not open video codec: %s", aBuf);
|
2016-08-27 19:10:27 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* allocate and init a re-usable frame */
|
|
|
|
m_VideoStream.frame = alloc_picture(c->pix_fmt, c->width, c->height);
|
|
|
|
if (!m_VideoStream.frame)
|
|
|
|
{
|
|
|
|
dbg_msg("video_recorder", "Could not allocate video frame");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If the output format is not YUV420P, then a temporary YUV420P
|
|
|
|
* picture is needed too. It is then converted to the required
|
|
|
|
* output format. */
|
|
|
|
m_VideoStream.tmp_frame = NULL;
|
|
|
|
if (c->pix_fmt != AV_PIX_FMT_YUV420P)
|
|
|
|
{
|
|
|
|
m_VideoStream.tmp_frame = alloc_picture(AV_PIX_FMT_YUV420P, c->width, c->height);
|
|
|
|
if (!m_VideoStream.tmp_frame)
|
|
|
|
{
|
|
|
|
dbg_msg("video_recorder", "Could not allocate temporary picture");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* copy the stream parameters to the muxer */
|
|
|
|
ret = avcodec_parameters_from_context(m_VideoStream.st->codecpar, c);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
dbg_msg("video_recorder", "Could not copy the stream parameters");
|
|
|
|
exit(1);
|
|
|
|
}
|
2019-10-31 07:36:38 +00:00
|
|
|
m_vseq = 0;
|
2016-08-27 19:10:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CVideo::open_audio()
|
|
|
|
{
|
|
|
|
AVCodecContext *c;
|
|
|
|
int nb_samples;
|
|
|
|
int ret;
|
|
|
|
AVDictionary *opt = NULL;
|
|
|
|
|
|
|
|
c = m_AudioStream.enc;
|
|
|
|
|
|
|
|
/* open it */
|
2019-10-31 14:01:12 +00:00
|
|
|
//m_dbgfile = fopen("/tmp/pcm_dbg", "wb");
|
2016-08-27 19:10:27 +00:00
|
|
|
av_dict_copy(&opt, m_pOptDict, 0);
|
|
|
|
ret = avcodec_open2(c, m_AudioCodec, &opt);
|
|
|
|
av_dict_free(&opt);
|
2016-08-31 12:09:06 +00:00
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
char aBuf[AV_ERROR_MAX_STRING_SIZE];
|
|
|
|
av_strerror(ret, aBuf, sizeof(aBuf));
|
|
|
|
dbg_msg("video_recorder", "Could not open audio codec: %s", aBuf);
|
2016-08-27 19:10:27 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)
|
|
|
|
nb_samples = 10000;
|
|
|
|
else
|
|
|
|
nb_samples = c->frame_size;
|
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
m_AudioStream.frame = alloc_audio_frame(c->sample_fmt, c->channel_layout, c->sample_rate, nb_samples);
|
|
|
|
|
2019-10-26 11:54:25 +00:00
|
|
|
m_AudioStream.tmp_frame = alloc_audio_frame(AV_SAMPLE_FMT_S16, AV_CH_LAYOUT_STEREO, g_Config.m_SndRate, m_SndBufferSize);
|
2016-08-27 19:10:27 +00:00
|
|
|
|
|
|
|
/* copy the stream parameters to the muxer */
|
|
|
|
ret = avcodec_parameters_from_context(m_AudioStream.st->codecpar, c);
|
|
|
|
if (ret < 0) {
|
|
|
|
dbg_msg("video_recorder", "Could not copy the stream parameters");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* create resampler context */
|
|
|
|
m_AudioStream.swr_ctx = swr_alloc();
|
|
|
|
if (!m_AudioStream.swr_ctx) {
|
|
|
|
dbg_msg("video_recorder", "Could not allocate resampler context");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set options */
|
2016-08-30 23:39:59 +00:00
|
|
|
av_opt_set_int (m_AudioStream.swr_ctx, "in_channel_count", 2, 0);
|
|
|
|
av_opt_set_int (m_AudioStream.swr_ctx, "in_sample_rate", g_Config.m_SndRate, 0);
|
2019-10-26 11:54:25 +00:00
|
|
|
av_opt_set_sample_fmt(m_AudioStream.swr_ctx, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
|
2016-08-27 19:10:27 +00:00
|
|
|
av_opt_set_int (m_AudioStream.swr_ctx, "out_channel_count", c->channels, 0);
|
|
|
|
av_opt_set_int (m_AudioStream.swr_ctx, "out_sample_rate", c->sample_rate, 0);
|
|
|
|
av_opt_set_sample_fmt(m_AudioStream.swr_ctx, "out_sample_fmt", c->sample_fmt, 0);
|
|
|
|
|
|
|
|
/* initialize the resampling context */
|
|
|
|
if ((ret = swr_init(m_AudioStream.swr_ctx)) < 0) {
|
|
|
|
dbg_msg("video_recorder", "Failed to initialize the resampling context");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Add an output stream. */
|
|
|
|
void CVideo::add_stream(OutputStream *ost, AVFormatContext *oc, AVCodec **codec, enum AVCodecID codec_id)
|
|
|
|
{
|
|
|
|
AVCodecContext *c;
|
|
|
|
|
|
|
|
/* find the encoder */
|
|
|
|
*codec = avcodec_find_encoder(codec_id);
|
|
|
|
if (!(*codec))
|
|
|
|
{
|
|
|
|
dbg_msg("video_recorder", "Could not find encoder for '%s'",
|
|
|
|
avcodec_get_name(codec_id));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
ost->st = avformat_new_stream(oc, NULL);
|
|
|
|
if (!ost->st)
|
|
|
|
{
|
|
|
|
dbg_msg("video_recorder", "Could not allocate stream");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
ost->st->id = oc->nb_streams-1;
|
|
|
|
c = avcodec_alloc_context3(*codec);
|
|
|
|
if (!c)
|
|
|
|
{
|
|
|
|
dbg_msg("video_recorder", "Could not alloc an encoding context");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
ost->enc = c;
|
|
|
|
|
|
|
|
switch ((*codec)->type)
|
|
|
|
{
|
2016-08-30 23:39:59 +00:00
|
|
|
case AVMEDIA_TYPE_AUDIO:
|
|
|
|
|
|
|
|
// m_MixingRate = g_Config.m_SndRate;
|
|
|
|
//
|
|
|
|
// // Set 16-bit stereo audio at 22Khz
|
|
|
|
// Format.freq = g_Config.m_SndRate; // ignore_convention
|
|
|
|
// Format.format = AUDIO_S16; // ignore_convention
|
|
|
|
// Format.channels = 2; // ignore_convention
|
|
|
|
// Format.samples = g_Config.m_SndBufferSize; // ignore_convention
|
|
|
|
|
|
|
|
c->sample_fmt = (*codec)->sample_fmts ? (*codec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;
|
|
|
|
c->bit_rate = g_Config.m_SndRate * 2 * 16;
|
|
|
|
c->frame_size = m_SndBufferSize;
|
|
|
|
c->sample_rate = g_Config.m_SndRate;
|
|
|
|
if ((*codec)->supported_samplerates)
|
2016-08-27 19:10:27 +00:00
|
|
|
{
|
2016-08-30 23:39:59 +00:00
|
|
|
c->sample_rate = (*codec)->supported_samplerates[0];
|
|
|
|
for (int i = 0; (*codec)->supported_samplerates[i]; i++)
|
|
|
|
{
|
|
|
|
if ((*codec)->supported_samplerates[i] == g_Config.m_SndRate)
|
|
|
|
c->sample_rate = g_Config.m_SndRate;
|
|
|
|
}
|
2016-08-27 19:10:27 +00:00
|
|
|
}
|
2016-08-30 23:39:59 +00:00
|
|
|
c->channels = 2;
|
|
|
|
c->channel_layout = AV_CH_LAYOUT_STEREO;
|
|
|
|
|
2016-08-31 20:04:40 +00:00
|
|
|
ost->st->time_base.num = 1;
|
|
|
|
ost->st->time_base.den = c->sample_rate;
|
2016-08-30 23:39:59 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case AVMEDIA_TYPE_VIDEO:
|
|
|
|
c->codec_id = codec_id;
|
|
|
|
|
|
|
|
c->bit_rate = 400000;
|
|
|
|
/* Resolution must be a multiple of two. */
|
|
|
|
c->width = m_Width;
|
2019-10-26 11:54:25 +00:00
|
|
|
c->height = m_Height%2==0?m_Height:m_Height-1;
|
2016-08-30 23:39:59 +00:00
|
|
|
/* timebase: This is the fundamental unit of time (in seconds) in terms
|
|
|
|
* of which frame timestamps are represented. For fixed-fps content,
|
|
|
|
* timebase should be 1/framerate and timestamp increments should be
|
|
|
|
* identical to 1. */
|
2016-08-31 20:04:40 +00:00
|
|
|
ost->st->time_base.num = 1;
|
|
|
|
ost->st->time_base.den = m_FPS;
|
2016-08-30 23:39:59 +00:00
|
|
|
c->time_base = ost->st->time_base;
|
|
|
|
|
|
|
|
c->gop_size = 12; /* emit one intra frame every twelve frames at most */
|
|
|
|
c->pix_fmt = STREAM_PIX_FMT;
|
|
|
|
if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO)
|
2016-08-27 19:10:27 +00:00
|
|
|
{
|
2016-08-30 23:39:59 +00:00
|
|
|
/* just for testing, we also add B-frames */
|
|
|
|
c->max_b_frames = 2;
|
|
|
|
}
|
|
|
|
if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO)
|
|
|
|
{
|
|
|
|
/* Needed to avoid using macroblocks in which some coeffs overflow.
|
|
|
|
* This does not happen with normal video, it just happens here as
|
|
|
|
* the motion of the chroma plane does not match the luma plane. */
|
|
|
|
c->mb_decision = 2;
|
|
|
|
}
|
|
|
|
if (codec_id == AV_CODEC_ID_H264)
|
|
|
|
{
|
|
|
|
av_opt_set(c->priv_data, "preset", "slow", 0);
|
|
|
|
av_opt_set(c->priv_data, "crf", "22", 0);
|
2016-08-27 19:10:27 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
default:
|
|
|
|
break;
|
2016-08-27 19:10:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Some formats want stream headers to be separate. */
|
|
|
|
if (oc->oformat->flags & AVFMT_GLOBALHEADER)
|
|
|
|
c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
void CVideo::write_frame(OutputStream* pStream)
|
|
|
|
{
|
|
|
|
|
2019-11-12 13:41:30 +00:00
|
|
|
int ret_recv = 0;
|
2016-08-30 23:39:59 +00:00
|
|
|
|
|
|
|
AVPacket Packet = { 0 };
|
|
|
|
|
|
|
|
av_init_packet(&Packet);
|
|
|
|
Packet.data = 0;
|
|
|
|
Packet.size = 0;
|
|
|
|
|
2019-11-12 13:41:30 +00:00
|
|
|
avcodec_send_frame(pStream->enc, pStream->frame);
|
2016-08-30 23:39:59 +00:00
|
|
|
do
|
|
|
|
{
|
|
|
|
ret_recv = avcodec_receive_packet(pStream->enc, &Packet);
|
|
|
|
if (!ret_recv)
|
|
|
|
{
|
|
|
|
/* rescale output packet timestamp values from codec to stream timebase */
|
2019-10-31 07:36:38 +00:00
|
|
|
av_packet_rescale_ts(&Packet, pStream->enc->time_base, pStream->st->time_base);
|
2016-08-30 23:39:59 +00:00
|
|
|
Packet.stream_index = pStream->st->index;
|
|
|
|
|
|
|
|
if (int ret = av_interleaved_write_frame(m_pFormatContext, &Packet))
|
|
|
|
{
|
2016-08-31 12:09:06 +00:00
|
|
|
char aBuf[AV_ERROR_MAX_STRING_SIZE];
|
|
|
|
av_strerror(ret, aBuf, sizeof(aBuf));
|
|
|
|
dbg_msg("video_recorder", "Error while writing video frame: %s", aBuf);
|
2016-08-30 23:39:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
} while (true);
|
|
|
|
|
|
|
|
if (ret_recv && ret_recv != AVERROR(EAGAIN))
|
|
|
|
{
|
|
|
|
dbg_msg("video_recorder", "Error encoding frame, error: %d", ret_recv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CVideo::finish_frames(OutputStream* pStream)
|
2016-08-27 19:10:27 +00:00
|
|
|
{
|
2016-08-30 23:39:59 +00:00
|
|
|
dbg_msg("video_recorder", "------------");
|
2019-11-12 13:41:30 +00:00
|
|
|
int ret_recv = 0;
|
2016-08-30 23:39:59 +00:00
|
|
|
|
|
|
|
AVPacket Packet = { 0 };
|
|
|
|
|
|
|
|
av_init_packet(&Packet);
|
|
|
|
Packet.data = 0;
|
|
|
|
Packet.size = 0;
|
2016-08-27 19:10:27 +00:00
|
|
|
|
2019-11-12 13:41:30 +00:00
|
|
|
avcodec_send_frame(pStream->enc, 0);
|
2016-08-30 23:39:59 +00:00
|
|
|
do
|
|
|
|
{
|
|
|
|
ret_recv = avcodec_receive_packet(pStream->enc, &Packet);
|
|
|
|
if (!ret_recv)
|
|
|
|
{
|
|
|
|
/* rescale output packet timestamp values from codec to stream timebase */
|
2019-10-31 07:36:38 +00:00
|
|
|
//if(pStream->st->codec->codec_type == AVMEDIA_TYPE_AUDIO)
|
|
|
|
av_packet_rescale_ts(&Packet, pStream->enc->time_base, pStream->st->time_base);
|
2016-08-30 23:39:59 +00:00
|
|
|
Packet.stream_index = pStream->st->index;
|
|
|
|
|
|
|
|
if (int ret = av_interleaved_write_frame(m_pFormatContext, &Packet))
|
|
|
|
{
|
2016-08-31 12:09:06 +00:00
|
|
|
char aBuf[AV_ERROR_MAX_STRING_SIZE];
|
|
|
|
av_strerror(ret, aBuf, sizeof(aBuf));
|
|
|
|
dbg_msg("video_recorder", "Error while writing video frame: %s", aBuf);
|
2016-08-30 23:39:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
} while (true);
|
|
|
|
|
|
|
|
if (ret_recv && ret_recv != AVERROR_EOF)
|
|
|
|
{
|
|
|
|
dbg_msg("video_recorder", "failed to finish recoding, error: %d", ret_recv);
|
|
|
|
}
|
2016-08-27 19:10:27 +00:00
|
|
|
}
|
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
void CVideo::close_stream(OutputStream* ost)
|
2016-08-27 19:10:27 +00:00
|
|
|
{
|
|
|
|
avcodec_free_context(&ost->enc);
|
|
|
|
av_frame_free(&ost->frame);
|
|
|
|
av_frame_free(&ost->tmp_frame);
|
|
|
|
sws_freeContext(ost->sws_ctx);
|
|
|
|
swr_free(&ost->swr_ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|