From 75be52dc7cf00b1119b1e2920f417ec55282820c Mon Sep 17 00:00:00 2001 From: "K.Ohta" Date: Fri, 27 May 2016 19:09:59 +0900 Subject: [PATCH] [Qt][MOVIE] Recorder: Initial. This still not working. --- .../cmake/toolchain_mingw_cross_linux.cmake | 8 +- source/src/qt/avio/movie_saver.cpp | 362 +++++++++++++++++++++ source/src/qt/avio/movie_saver.h | 90 +++++ 3 files changed, 456 insertions(+), 4 deletions(-) create mode 100644 source/src/qt/avio/movie_saver.cpp create mode 100644 source/src/qt/avio/movie_saver.h diff --git a/source/build-cmake/cmake/toolchain_mingw_cross_linux.cmake b/source/build-cmake/cmake/toolchain_mingw_cross_linux.cmake index 3ea38373a..29bf4a5c5 100644 --- a/source/build-cmake/cmake/toolchain_mingw_cross_linux.cmake +++ b/source/build-cmake/cmake/toolchain_mingw_cross_linux.cmake @@ -13,14 +13,14 @@ if(USE_SDL2) /usr/local/i586-mingw-msvc /usr/local/i586-mingw-msvc/SDL2/i686-w64-mingw32 /usr/local/i586-mingw-msvc/5.5/mingw492_32 -# /usr/local/i586-mingw-msvc/5.5/mingw500_32 +# /usr/local/i586-mingw-msvc/5.5/mingw530_32_win32 ) else() SET(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32 /usr/local/i586-mingw-msvc /usr/local/i586-mingw-msvc/SDL1/ /usr/local/i586-mingw-msvc/5.5/mingw492_32 -# /usr/local/i586-mingw-msvc/5.5/mingw500_32 +# /usr/local/i586-mingw-msvc/5.5/mingw530_32_win32 ) endif() @@ -44,6 +44,6 @@ set(SDL_INCLUDE_DIRS /usr/local/i586-mingw-msvc/SDL1/include/SDL) set(SDLMAIN_LIBRARY "") set(ADDITIONAL_LIBRARIES libwinmm.a) -set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++") +#set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++") #set(CMAKE_EXE_LINKER_FLAGS "-static-libstdc++") -#set(CMAKE_EXE_LINKER_FLAGS "") +set(CMAKE_EXE_LINKER_FLAGS "") diff --git a/source/src/qt/avio/movie_saver.cpp b/source/src/qt/avio/movie_saver.cpp new file mode 100644 index 000000000..7d28915d5 --- /dev/null +++ b/source/src/qt/avio/movie_saver.cpp @@ -0,0 +1,362 @@ +/* + * Common Source Code Project for Qt : movie saver. + * (C) 2016 K.Ohta + * License: GPLv2 + * History: May 27, 2016 : Initial. This refer from avidemux 2.5.6 . + */ + +#include +#include "movie_saver.h" +#include "osd.h" + +MOVIE_SAVER::MOVIE_SAVER(int width, int height, int fps, OSD *osd) : QThread(0) +{ + buffer_size=8 * 1024 * 224; + max_rate=4000 * 1000; + min_rate=0; + bitrate = 2000 * 1000; + _width = width; + _height = height; + rec_fps = fps; + p_osd = osd; + recording = false; + + audio_data_queue.clear(); + + video_data_queue.clear(); + video_width_queue.clear(); + video_height_queue.clear(); + + totalSrcFrame = 0; + totalDstFrame = 0; + totalAudioFrame = 0; + bRunThread = false; +} + +MOVIE_SAVER::~MOVIE_SAVER() +{ + do_close(); +} + +void MOVIE_SAVER::enqueue_video(QByteArray *p, int width, int height) +{ + if(!recording) return false; + if(p == NULL) return; + QByteArray *pp = new QByteArray(p->data(), p->size()); + + video_data_queue.enqueue(pp); + video_width_queue.enqueue(width); + video_height_queue.enqueue(height); +} + +bool MOVIE_SAVER::dequeue_video(uint32_t *p) +{ + if(!recording) return false; + if(video_data_queue.isEmpty()) return false; + if(p == NULL) return false; + + QByteArray *pp = video_data_queue.dequeue(); + if((pp == NULL) || (video_size <= 0)) return false; + + video_size = pp->size(); + memcpy(p, pp->data(), video_size); + delete pp; + + return true; +} + +void MOVIE_SAVER::enqueue_audio(QByteArray *p) +{ + if(!recording) return; + if(p == NULL) return; + QByteArray *pp = new QByteArray(p->data(), p->size()); + audio_data_queue.enqueue(pp); +} + +bool MOVIE_SAVER::dequeue_audio(int16_t *p) +{ + if(!recording) return false; + if(audio_data_queue.isEmpty()) return false; + if(p == NULL) return false; + + QByteArray *pp = audio_data_queue.dequeue(); + if((pp == NULL) || (audio_size <= 0)) return false; + + audio_size = pp->size(); + memcpy(p, pp->data(), audio_size); + delete pp; + return true; +} + +void MOVIE_SAVER::run() +{ + bRunThread = true; + + int fps_wait = (int)((1000.0 / this->vm_frame_rate()) / 2.0); + int tmp_wait = fps_wait; + while(bRunThread) { + if(recording) { + if(!bRunThread) break; + if(!recording) break; + if(!audio_data_queue.isEmpty()) { + if(dequeue_audio(audio_frame)) { + AVPacket pkt; + uint64_t bytes = audio_size; + uint64_t us = (uint64_t)floor(((double)bytes * 1000000.0) / (double)audio_codec->sample_rate); + double samples = ((double)us / 1000000.0) * (double)audio_codec->sample_rate() + int ret; + + if(bytes == 0) goto _video; + + av_init_packet(&pkt); + pkt.dts = pkt.pts = (int64_t)samples; + pkt.flags |= AV_PKT_FLAG_KEY; + pkt.data = (uint8_t *)audio_frame; + pkt.size = (uint32_t)bytes; + pkt.stream_index = 1; + ret = av_write_frame(output_context, &pkt); + + totalAudioFrame++; + } + } + _video: +#if 0 // ToDo + if(!bRunThread) break; + if(!recording) break; + if(!video_data_queue.isEmpty() && + !video_height_queue.isEmpty() && !video_width_queue.isEmpty()) { + // Scale Video + if(dequeue_video(video_frame)) { + AVRational fps = {1, 1000000}; + AVPacket pkt; + uint64_t bytes = video_size; + int i; + av_init_packet(&pkt); + // Call encoder + + // Call muxer + + totalSrcFrame++; + totalDstFrame++; + } + } +#endif + } + if(fps_wait >= tmp_wait) { + this->msleep(tmp_wait); + tmp_wait = 0; + } else { + this->msleep(fps_wait); + tmp_wait -= fps_wait; + } + if(tmp_wait <= 0) { + fps_wait = (int)((1000.0 / this->vm_frame_rate()) / 2.0); + tmp_wait = fps_wait; + } + } +} + +void MOVIE_SAVER::do_close() +{ + if(output_context != NULL) { + av_write_trailer(output_context); + avio_close(output_context->pb); + } + if(audio_stream != NULL) { + av_free(audio_stream); + } + if(video_stream != NULL) { + av_free(video_stream); + } + audio_stream = NULL; + video_stream = NULL; + + if(output_context != NULL) { + av_free(output_context); + output_context = NULL; + } + recording = false; + + audio_data_queue.clear(); + + video_data_queue.clear(); + video_width_queue.clear(); + video_height_queue.clear(); + + // Message + AGAR_DebugLog(AGAR_LOG_DEBUG, "MOVIE: Close: Read: Video %ll frames, Audio %ll frames", totalSrcFrame, totalAudioFrame); + AGAR_DebugLog(AGAR_LOG_DEBUG, "MOVIE: Close: Write: Video %ll frames, Audio %ll frames", totalDstFrame, totalAudioFrame); + totalSrcFrame = 0; + totalDstFrame = 0; + totalAudioFrame = 0; +} + +QString MOVIE_PLAYER::create_date_file_name(void) +{ + QDateTime nowTime = QDateTime::currentDateTime(); + QString tmps = nowTime.toString(QString::fromUtf8("yyyy-MM-dd_hh-mm-ss.zzz.")); + return tmps; +} + +bool MOVIE_SAVER::is_recording(void) +{ + return recording; +} +static const AVRational time_base_15 = (AVRational){1001, 14485}; +static const AVRational time_base_24 = (AVRational){1001, 23976}; +static const AVRational time_base_25 = (AVRational){1001, 25025}; +static const AVRational time_base_30 = (AVRational){1001, 29970}; +static const AVRational time_base_60 = (AVRational){1001, 59940}; + +void MOVIE_SAVER::do_open(QString filename) +{ + cur_time_t *t_time; + do_close(); + + _filename = filename; + format = av_guess_format("mp4", NULL, NULL); + + if(format == NULL) { + AGAR_DebugLog(AGAR_LOG_DEBUG, "AVC ERROR: Failed to initialize libavf"); + return; + } + + output_context = avformat_alloc_context(); + if(output_context == NULL) { + AGAR_DebugLog(AGAR_LOG_DEBUG, "AVC ERROR: Failed to get output_context"); + do_close(); + return; + } + + output_context->oformat = format; + snprintf(output_context->filename, 1000, "file://%s", filename.fromUtf8().constData()); + AGAR_DebugLog(AGAR_LOG_DEBUG, "Start rec VIDEO: %s", output_context->filename); + +#if 0 // Todo + video_stream = av_new_stream(output_context, 0); + if(video_stream == NULL) { + AGAR_DebugLog(AGAR_LOG_DEBUG, "AVC ERROR: Failed to open video stream"); + do_close(); + return; + } + + get_host_time(t_time); + video_codec = video_stream->codec; + video_codec->gop_size = 15; + video_codec->max_b_frames = 8; + video_codec->has_b_frames = 1; + QString author = QString::fromUtf8("emu"); + author = author + osd->get_vm_config_name(); + QString date_str = QString::fromUtf8("Record from"); + date_str = date_str + create_date_file_name(); + + { // Video Start + av_dict_set(&output_context->metadata, "title", date_str.fromUtf8().constData(), 0); + av_dict_set(&output_context->metadata, "author", author.fromUtf8().constData(), 0); + video_codec->has_b_frames=2; // let muxer know we may have bpyramid + video_codec->codec_id = CODEC_ID_H264; + video_codec->codec = &codec_real; + memset(video_codec->codec, 0, sizeof(struct AVCodec)); + strcpy(video_codec->codec->name, "H264"); + { + // Set basic rate + video_codec->rc_buffer_size = buffer_size; + video_codec->rc_max_rate = max_rate; + video_codec->rc_min_rate = min_rate; + video_codec->bit_rate = bitrate; + } + video_codec->codec_type = AVMEDIA_TYPE_VIDEO; + video_codec->flags = CODEC_FLAG_QSCALE; + video_codec->width = _width; + video_codec->height = _height; + } + + switch(rec_fps) { + case 15: + video_codec->time_base = time_base_15; + video_codec->bit_rate = bitrate / 2; + video_codec->rc_max_rate = max_rate / 2; + video_codec->rc_min_rate = min_rate / 2; + break; + case 24: + video_codec->time_base = time_base_24; + break; + case 25: + video_codec->time_base = time_base_25; + break; + case 30: + video_codec->time_base = time_base_30; + break; + case 60: + video_codec->time_base = time_base_60; + video_codec->bit_rate = bitrate * 2; + video_codec->rc_max_rate = max_rate * 2; + video_codec->rc_min_rate = min_rate * 2; + break; + default: + time_base = (AVRational){1001, rec_fps * 1000}; + video_codec->time_base = time_base; + video_codec->bit_rate = (int)((double)bitrate * 30.0 / (double)rec_fps); + video_codec->rc_max_rate = (int)((double)max_rate * 30.0 / (double)rec_fps); + video_codec->rc_min_rate = (int)((double)min_rate * 30.0 / (double)rec_fps); + break; + } +#endif // ToDo + + //audio_stream = av_new_stream(output_context, 1); + audio_stream = av_new_stream(output_context, 0); + if(audio_stream == NULL) { + AGAR_DebugLog(AGAR_LOG_DEBUG, "AVC ERROR: Failed to open audio stream"); + do_close(); + return; + } + + // Temporally using libAV's AAC + audio_codec = audio_stream->codec; + audio_codec->frame_size = 1024; + audio_codec->codec_id = CODEC_ID_AAC; + audio_codec->sample_rate = osd->get_sound_rate(); + + audio_codec->codec_type = AVMEDIA_TYPE_AUDIO; + + audio_codec->bit_rate = audio_codec->sample_rate * 8 * 2; + audio_codec->rc_buffer_size = audio_codec->sample_rate / 4; // 250 ms worth + audio_codec->channels = 2; + + // Context + output_context->mux_rate = 100080 * 1000; + output_context->pre_load = AV_TIME_BASE / 10; + output_context->max_delay = 100 * 1000; // MAX 100ms delay; + + if(avio_open(&(output_context->pb), filename.fromUtf8().constData(), AVIO_FLAG_WRITE) < 0) { + AGAR_DebugLog(AGAR_LOG_DEBUG, "AVC ERROR: Failed to open file"); + do_close(); + return; + } + + av_dump_format(output_context, 0, filename.fromUtf8().constData(), 1); + AGAR_DebugLog(AGAR_LOG_DEBUG, "Successfully opened AVC stream."); + + recording = true; +} + +void MOVIE_SAVER::do_exit() +{ + bRunThread = false; +} + +void MOVIE_SAVER::do_set_record_fps(int fps) +{ + if((fps > 0) && (fps <= 60)) rec_fps = fps; +} + +void MOVIE_SAVER::do_set_width(int width) +{ + _width = width; +} + +void MOVIE_SAVER::do_set_height(int height) +{ + _height = height; +} + diff --git a/source/src/qt/avio/movie_saver.h b/source/src/qt/avio/movie_saver.h new file mode 100644 index 000000000..32432321d --- /dev/null +++ b/source/src/qt/avio/movie_saver.h @@ -0,0 +1,90 @@ +/* + * Common Source Code Project for Qt : movie saver. + * (C) 2016 K.Ohta + * License: GPLv2 + * History: May 27, 2016 : Initial. This refer from avidemux 2.5.6 . + */ + +#ifndef _QT_MOVIE_SAVER_H +#define _QT_MOVIE_SAVER_H + +#include +#include +#include +#include + +extern "C" { + #include "libavformat/avformat.h" +} + +class OSD; + +QT_BEGIN_NAMESPACE +class MOVIE_SAVER: public QThread +{ + Q_OBJECT +protected: + OSD *p_osd; + AVOutputFormat *format; + AVCodecContext *codec; + AVStream *audio_stream; + AVStream *video_stream; + ADM_MUXER_TYPE muxer_type; + + AVFormatContext *output_context; + struct AVCodec codec_real; + QString _filename; + bool bRunThread; + + uint min_rate; + uint max_rate; + uint buffer_size; + uint bitrate; + int _width; + int _height; + bool recording; + int rec_fps; + + AVRational time_base; + + uint64_t audio_size; + uint64_t video_size; + uint32_t ptsFrame; + + uint64_t totalSrcFrame; + uint64_t totalDstFrame; + uint64_t totalAudioFrame; + + int16_t audio_frame[2 * 48000 * sizeof(int16_t)]; // 1Sec + uint32_t video_frame[1280 * 512 * sizeof(uint32_t)]; // 1 frame : right? + uint32_t video_dst[1280 * 1024 * sizeof(uint32_t)]; // 1 frame : right? + + QQueue video_width_queue; + QQueue video_height_queue; + QQueue video_data_queue; + + QQueue audio_data_queue; + + dequeue_audio(int16_t *p); + dequeue_video(uint32_t *p); + QString create_date_file_name(void); + +public: + MOVIE_SAVER(int width, int height, int fps, OSD *osd); + ~MOVIE_SAVER(); + void doWork(); + bool is_recording(void); + +public slots: + void enqueue_video(QByteArray *p, int width, int height); + void enqueue_audio(QByteArray *p); + void do_close(); + void do_open(QString filename); + void do_set_width(int width); + void do_set_height(int height); + void do_set_record_fps(int fps) + void do_exit(); +}; +QT_END_NAMESPACE + +#endif -- 2.11.0