├── AV └── Output │ ├── BaseEncoder.cpp │ ├── BaseEncoder.h │ ├── Muxer.cpp │ ├── Muxer.h │ ├── OutputSettings.h │ ├── VideoEncoder.cpp │ └── VideoEncoder.h ├── Logger.cpp ├── Logger.h ├── README.md ├── TempBuffer.h ├── global.h ├── grab_test.pro ├── main.cpp ├── maintest.cpp ├── maintest.h ├── maintest.ui ├── test.mkv ├── xgrab.cpp └── xgrab.h /AV/Output/BaseEncoder.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2016 Maarten Baert 3 | 4 | This file is part of SimpleScreenRecorder. 5 | 6 | SimpleScreenRecorder is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | SimpleScreenRecorder is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with SimpleScreenRecorder. If not, see . 18 | */ 19 | 20 | #include "global.h" 21 | #include "BaseEncoder.h" 22 | 23 | #include "Logger.h" 24 | #include "Muxer.h" 25 | 26 | int ParseCodecOptionInt(const QString& key, const QString& value, int min, int max, int multiply) { 27 | bool parsed; 28 | int value_int = value.toInt(&parsed); 29 | if(!parsed) { 30 | Logger::LogError("[ParseCodecOptionInt] " + Logger::tr("Error: Option '%1' could not be parsed!").arg(key)); 31 | throw LibavException(); 32 | } 33 | return clamp(value_int, min, max) * multiply; 34 | } 35 | double ParseCodecOptionDouble(const QString& key, const QString& value, double min, double max, double multiply) { 36 | bool parsed; 37 | double value_double = value.toDouble(&parsed); 38 | if(!parsed) { 39 | Logger::LogError("[ParseCodecOptionDouble] " + Logger::tr("Error: Option '%1' could not be parsed!").arg(key)); 40 | throw LibavException(); 41 | } 42 | return clamp(value_double, min, max) * multiply; 43 | } 44 | 45 | BaseEncoder::BaseEncoder(Muxer* muxer, AVStream* stream, AVCodecContext* codec_context, AVCodec* codec, AVDictionary** options) { 46 | 47 | m_muxer = muxer; 48 | m_stream = stream; 49 | m_codec_context = codec_context; 50 | m_codec_opened = false; 51 | 52 | // initialize shared data 53 | { 54 | SharedLock lock(&m_shared_data); 55 | lock->m_total_frames = 0; 56 | lock->m_total_packets = 0; 57 | lock->m_stats_actual_frame_rate = 0.0; 58 | lock->m_stats_previous_pts = AV_NOPTS_VALUE; 59 | lock->m_stats_previous_frames = 0; 60 | } 61 | 62 | // initialize thread signals 63 | m_should_stop = false; 64 | m_should_finish = false; 65 | m_is_done = false; 66 | m_error_occurred = false; 67 | 68 | try { 69 | Init(codec, options); 70 | } catch(...) { 71 | Free(); 72 | throw; 73 | } 74 | 75 | } 76 | 77 | BaseEncoder::~BaseEncoder() { 78 | Free(); 79 | } 80 | 81 | // Why can't this be done in the constructor/destructor? The problem is that a real destructor gets called *after* the derived class has already been destructed. 82 | // This means virtual function calls, like EncodeFrame, will fail. This is a problem since the encoder thread doesn't know that the derived class is gone. 83 | // A similar thing can happen with the constructor. To fix this, the derived class should call StartThread() and StopThread() manually. 84 | 85 | void BaseEncoder::StartThread() { 86 | 87 | // start encoder thread 88 | m_thread = std::thread(&BaseEncoder::EncoderThread, this); 89 | 90 | } 91 | 92 | void BaseEncoder::StopThread() { 93 | 94 | // tell the thread to stop 95 | if(m_thread.joinable()) { 96 | Logger::LogInfo("[BaseEncoder::~BaseEncoder] " + Logger::tr("Stopping encoder thread ...")); 97 | m_should_stop = true; 98 | m_thread.join(); 99 | } 100 | 101 | // free everything 102 | Free(); 103 | 104 | } 105 | 106 | double BaseEncoder::GetActualFrameRate() { 107 | SharedLock lock(&m_shared_data); 108 | return lock->m_stats_actual_frame_rate; 109 | } 110 | 111 | uint64_t BaseEncoder::GetTotalFrames() { 112 | SharedLock lock(&m_shared_data); 113 | return lock->m_total_frames; 114 | } 115 | 116 | unsigned int BaseEncoder::GetFrameLatency() { 117 | SharedLock lock(&m_shared_data); 118 | return (lock->m_total_frames > lock->m_total_packets)? lock->m_total_frames - lock->m_total_packets : 0; 119 | } 120 | 121 | unsigned int BaseEncoder::GetQueuedFrameCount() { 122 | SharedLock lock(&m_shared_data); 123 | return lock->m_frame_queue.size(); 124 | } 125 | 126 | unsigned int BaseEncoder::GetQueuedPacketCount() { 127 | return GetMuxer()->GetQueuedPacketCount(GetStream()->index); 128 | } 129 | 130 | void BaseEncoder::AddFrame(std::unique_ptr frame) { 131 | assert(frame->pts != (int64_t) AV_NOPTS_VALUE); 132 | SharedLock lock(&m_shared_data); 133 | ++lock->m_total_frames; 134 | if(lock->m_stats_previous_pts == (int64_t) AV_NOPTS_VALUE) { 135 | lock->m_stats_previous_pts = frame->pts; 136 | lock->m_stats_previous_frames = lock->m_total_frames; 137 | } 138 | double timedelta = (double) (frame->pts - lock->m_stats_previous_pts) * ToDouble(m_codec_context->time_base); 139 | if(timedelta > 0.999999) { 140 | lock->m_stats_actual_frame_rate = (double) (lock->m_total_frames - lock->m_stats_previous_frames) / timedelta; 141 | lock->m_stats_previous_pts = frame->pts; 142 | lock->m_stats_previous_frames = lock->m_total_frames; 143 | } 144 | lock->m_frame_queue.push_back(std::move(frame)); 145 | } 146 | 147 | void BaseEncoder::Finish() { 148 | m_should_finish = true; 149 | } 150 | 151 | void BaseEncoder::Stop() { 152 | m_should_stop = true; 153 | } 154 | 155 | void BaseEncoder::IncrementPacketCounter() { 156 | SharedLock lock(&m_shared_data); 157 | ++lock->m_total_packets; 158 | } 159 | 160 | void BaseEncoder::Init(AVCodec* codec, AVDictionary** options) { 161 | 162 | // open codec 163 | if(avcodec_open2(m_codec_context, codec, options) < 0) { 164 | Logger::LogError("[BaseEncoder::Init] " + Logger::tr("Error: Can't open codec!")); 165 | throw LibavException(); 166 | } 167 | m_codec_opened = true; 168 | 169 | // show a warning for every option that wasn't recognized 170 | AVDictionaryEntry *t = NULL; 171 | while((t = av_dict_get(*options, "", t, AV_DICT_IGNORE_SUFFIX)) != NULL) { 172 | Logger::LogWarning("[BaseEncoder::Init] " + Logger::tr("Warning: Codec option '%1' was not recognised!").arg(t->key)); 173 | } 174 | 175 | } 176 | 177 | void BaseEncoder::Free() { 178 | if(m_codec_opened) { 179 | avcodec_close(m_codec_context); 180 | m_codec_opened = false; 181 | } 182 | } 183 | 184 | void BaseEncoder::EncoderThread() { 185 | 186 | try { 187 | 188 | Logger::LogInfo("[BaseEncoder::EncoderThread] " + Logger::tr("Encoder thread started.")); 189 | 190 | // normal encoding 191 | while(!m_should_stop) { 192 | 193 | // get a frame 194 | std::unique_ptr frame; 195 | { 196 | SharedLock lock(&m_shared_data); 197 | if(!lock->m_frame_queue.empty()) { 198 | frame = std::move(lock->m_frame_queue.front()); 199 | lock->m_frame_queue.pop_front(); 200 | } 201 | } 202 | if(frame == NULL) { 203 | if(m_should_finish) { 204 | break; 205 | } 206 | usleep(20000); 207 | continue; 208 | } 209 | 210 | // encode the frame 211 | EncodeFrame(frame.get()); 212 | 213 | } 214 | 215 | // flush the encoder 216 | if(!m_should_stop && (m_codec_context->codec->capabilities & CODEC_CAP_DELAY)) { 217 | Logger::LogInfo("[BaseEncoder::EncoderThread] " + Logger::tr("Flushing encoder ...")); 218 | while(!m_should_stop) { 219 | if(!EncodeFrame(NULL)) { 220 | break; 221 | } 222 | } 223 | } 224 | 225 | // tell the others that we're done 226 | m_is_done = true; 227 | 228 | Logger::LogInfo("[BaseEncoder::EncoderThread] " + Logger::tr("Encoder thread stopped.")); 229 | 230 | } catch(const std::exception& e) { 231 | m_error_occurred = true; 232 | Logger::LogError("[BaseEncoder::EncoderThread] " + Logger::tr("Exception '%1' in encoder thread.").arg(e.what())); 233 | } catch(...) { 234 | m_error_occurred = true; 235 | Logger::LogError("[BaseEncoder::EncoderThread] " + Logger::tr("Unknown exception in encoder thread.")); 236 | } 237 | 238 | // always end the stream, even if there was an error, otherwise the muxer will wait forever 239 | m_muxer->EndStream(m_stream->index); 240 | 241 | } 242 | -------------------------------------------------------------------------------- /AV/Output/BaseEncoder.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2016 Maarten Baert 3 | 4 | This file is part of SimpleScreenRecorder. 5 | 6 | SimpleScreenRecorder is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | SimpleScreenRecorder is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with SimpleScreenRecorder. If not, see . 18 | */ 19 | 20 | #pragma once 21 | #include "global.h" 22 | 23 | #include "MutexDataPair.h" 24 | 25 | int ParseCodecOptionInt(const QString& key, const QString& value, int min, int max, int multiply = 1); 26 | double ParseCodecOptionDouble(const QString& key, const QString& value, double min, double max, double multiply = 1.0); 27 | 28 | class Muxer; 29 | 30 | class BaseEncoder { 31 | 32 | private: 33 | struct SharedData { 34 | std::deque > m_frame_queue; 35 | uint64_t m_total_frames, m_total_packets; 36 | double m_stats_actual_frame_rate; 37 | int64_t m_stats_previous_pts; 38 | uint64_t m_stats_previous_frames; 39 | }; 40 | typedef MutexDataPair::Lock SharedLock; 41 | 42 | private: 43 | Muxer *m_muxer; 44 | AVStream *m_stream; 45 | AVCodecContext *m_codec_context; 46 | bool m_codec_opened; 47 | 48 | std::thread m_thread; 49 | MutexDataPair m_shared_data; 50 | std::atomic m_should_stop, m_should_finish, m_is_done, m_error_occurred; 51 | 52 | protected: 53 | BaseEncoder(Muxer* muxer, AVStream* stream, AVCodecContext* codec_context, AVCodec* codec, AVDictionary** options); 54 | 55 | public: 56 | virtual ~BaseEncoder(); // encoders will be deleted by Muxer, don't delete them yourself! 57 | 58 | protected: 59 | void StartThread(); 60 | void StopThread(); // important: call this in the destructor of the derived class 61 | 62 | public: 63 | // Returns the frame rate of the output stream. 64 | // This function is thread-safe. 65 | double GetActualFrameRate(); 66 | 67 | // Returns the total number of added frames. 68 | // This function is thread-safe. 69 | uint64_t GetTotalFrames(); 70 | 71 | // Returns the current input-to-output latency of the encoder (in frames). 72 | // This function is thread-safe. 73 | unsigned int GetFrameLatency(); 74 | 75 | // Returns the total number of frames in the queue. 76 | // This function is thread-safe. 77 | unsigned int GetQueuedFrameCount(); 78 | 79 | unsigned int GetQueuedPacketCount(); 80 | 81 | public: // internal 82 | 83 | // Adds a frame to the frame queue. Called by the synchronizer. 84 | // This function is thread-safe. 85 | void AddFrame(std::unique_ptr frame); 86 | 87 | // Tells the encoder to stop. It can still take some time before the encoder is actually done. Called by the muxer. 88 | // After calling this function, the muxer will wait until either IsDone or HasErrorOccurred returns true. 89 | // This function is thread-safe. 90 | void Finish(); 91 | 92 | // Same as finish, except that queued frames will be dropped and the encoder won't be flushed. Called by the muxer. 93 | // This function is thread-safe. 94 | void Stop(); 95 | 96 | // Returns whether the encoding is done. If this returns true, the object can be deleted. Called by the muxer. 97 | // Note: If an error occurred during encoding, this function will return false. 98 | // This function is thread-safe and lock-free. 99 | inline bool IsDone() { return m_is_done; } 100 | 101 | // Returns whether an error has occurred in the input thread. Called by the muxer. 102 | // This function is thread-safe and lock-free. 103 | inline bool HasErrorOccurred() { return m_error_occurred; } 104 | 105 | inline Muxer* GetMuxer() { return m_muxer; } 106 | inline AVStream* GetStream() { return m_stream; } 107 | inline AVCodecContext* GetCodecContext() { return m_codec_context; } 108 | 109 | // Called by the encoder thread to encode a single frame. Frame can be NULL if the encoder uses delayed packets. 110 | // Returns whether a packet was created. 111 | virtual bool EncodeFrame(AVFrame* frame) = 0; 112 | 113 | protected: 114 | 115 | // Called to increment the packet counter. 116 | void IncrementPacketCounter(); 117 | 118 | private: 119 | void Init(AVCodec* codec, AVDictionary** options); 120 | void Free(); 121 | 122 | void EncoderThread(); 123 | 124 | }; 125 | -------------------------------------------------------------------------------- /AV/Output/Muxer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2016 Maarten Baert 3 | 4 | This file is part of SimpleScreenRecorder. 5 | 6 | SimpleScreenRecorder is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | SimpleScreenRecorder is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with SimpleScreenRecorder. If not, see . 18 | */ 19 | 20 | #include "global.h" 21 | #include "Muxer.h" 22 | 23 | #include "Logger.h" 24 | #include "BaseEncoder.h" 25 | #include "VideoEncoder.h" 26 | //#include "AudioEncoder.h" 27 | 28 | static void Free_avpacket(AVPacket *packet){ 29 | 30 | #if SSR_USE_AV_PACKET_ALLOC 31 | // This isn't documented anywhere (as usual) and the FFmpeg examples haven't been updated to their latest API yet, 32 | // but my guess is that it is safe to call this function even if the data has also been sent to the muxer, 33 | // because the internal reference counting in FFmpeg should ensure that the data is not deleted when it is still needed. 34 | // This wasn't the case with their old API which didn't do reference counting yet. 35 | av_packet_free(&packet); 36 | #else 37 | if(m_free_on_destruct) 38 | av_free_packet(packet); 39 | delete packet; 40 | #endif 41 | } 42 | Muxer::Muxer(const QString& container_name, const QString& output_file) { 43 | 44 | m_container_name = container_name; 45 | m_output_file = output_file; 46 | 47 | m_format_context = NULL; 48 | m_started = false; 49 | m_should_stop = true; 50 | // initialize stream data 51 | for(int i = 0; i < MUXER_MAX_STREAMS; ++i) { 52 | StreamLock lock(&m_stream_data[i]); 53 | lock->m_is_done = false; 54 | m_encoders[i] = NULL; 55 | } 56 | 57 | // initialize shared data 58 | { 59 | SharedLock lock(&m_shared_data); 60 | lock->m_total_bytes = 0; 61 | lock->m_stats_actual_bit_rate = 0.0; 62 | lock->m_stats_previous_time = NOPTS_DOUBLE; 63 | lock->m_stats_previous_bytes = 0; 64 | } 65 | 66 | // initialize thread signals 67 | m_is_done = false; 68 | m_error_occurred = false; 69 | 70 | try { 71 | Init(); 72 | } catch(...) { 73 | Free(); 74 | throw; 75 | } 76 | 77 | } 78 | 79 | Muxer::~Muxer() { 80 | 81 | if(m_started) { 82 | 83 | // stop the encoders 84 | Logger::LogInfo("[Muxer::~Muxer] " + Logger::tr("Stopping encoders ...")); 85 | for(unsigned int i = 0; i < m_format_context->nb_streams; ++i) { 86 | m_encoders[i]->Stop(); // no deadlock: nothing in Muxer is locked in this thread (and BaseEncoder::Stop is lock-free, but that could change) 87 | } 88 | 89 | m_should_stop = true; 90 | // wait for the thread to stop 91 | if(m_thread.joinable()) { 92 | Logger::LogInfo("[Muxer::~Muxer] " + Logger::tr("Waiting for muxer thread to stop ...")); 93 | m_thread.join(); 94 | } 95 | 96 | } 97 | 98 | // free everything 99 | Free(); 100 | 101 | } 102 | 103 | VideoEncoder* Muxer::AddVideoEncoder(const QString& codec_name, const std::vector >& codec_options, 104 | unsigned int bit_rate, unsigned int width, unsigned int height, unsigned int frame_rate) { 105 | AVCodec *codec = FindCodec(codec_name); 106 | AVCodecContext *codec_context = NULL; 107 | AVStream *stream = AddStream(codec, &codec_context); 108 | VideoEncoder *encoder; 109 | AVDictionary *options = NULL; 110 | try { 111 | VideoEncoder::PrepareStream(stream, codec_context, codec, &options, codec_options, bit_rate, width, height, frame_rate); 112 | m_encoders[stream->index] = encoder = new VideoEncoder(this, stream, codec_context, codec, &options); 113 | #if SSR_USE_AVSTREAM_CODECPAR 114 | if(avcodec_parameters_from_context(stream->codecpar, codec_context) < 0) { 115 | Logger::LogError("[Muxer::AddVideoEncoder] " + Logger::tr("Error: Can't copy parameters to stream!")); 116 | throw LibavException(); 117 | } 118 | #endif 119 | av_dict_free(&options); 120 | } catch(...) { 121 | av_dict_free(&options); 122 | throw; 123 | } 124 | return encoder; 125 | } 126 | 127 | /*AudioEncoder* Muxer::AddAudioEncoder(const QString& codec_name, const std::vector >& codec_options, 128 | unsigned int bit_rate, unsigned int channels, unsigned int sample_rate) { 129 | AVCodec *codec = FindCodec(codec_name); 130 | AVCodecContext *codec_context = NULL; 131 | AVStream *stream = AddStream(codec, &codec_context); 132 | AudioEncoder *encoder; 133 | AVDictionary *options = NULL; 134 | try { 135 | AudioEncoder::PrepareStream(stream, codec_context, codec, &options, codec_options, bit_rate, channels, sample_rate); 136 | m_encoders[stream->index] = encoder = new AudioEncoder(this, stream, codec_context, codec, &options); 137 | #if SSR_USE_AVSTREAM_CODECPAR 138 | if(avcodec_parameters_from_context(stream->codecpar, codec_context) < 0) { 139 | Logger::LogError("[Muxer::AddAudioEncoder] " + Logger::tr("Error: Can't copy parameters to stream!")); 140 | throw LibavException(); 141 | } 142 | #endif 143 | av_dict_free(&options); 144 | } catch(...) { 145 | av_dict_free(&options); 146 | throw; 147 | } 148 | return encoder; 149 | }*/ 150 | 151 | void Muxer::Start() { 152 | assert(!m_started); 153 | 154 | // make sure all encoders were created successfully 155 | for(unsigned int i = 0; i < m_format_context->nb_streams; ++i) { 156 | assert(m_encoders[i] != NULL); 157 | } 158 | 159 | // write header 160 | if(avformat_write_header(m_format_context, NULL) != 0) { 161 | Logger::LogError("[Muxer::Start] " + Logger::tr("Error: Can't write header!", "Don't translate 'header'")); 162 | throw LibavException(); 163 | } 164 | char sdp[2048]; 165 | av_sdp_create(&m_format_context,1, sdp, sizeof(sdp)); 166 | printf("%s\n",sdp); 167 | fflush(stdout); 168 | 169 | m_started = true; 170 | m_should_stop = false; 171 | m_thread = std::thread(&Muxer::MuxerThread, this); 172 | 173 | } 174 | 175 | void Muxer::Finish() { 176 | assert(m_started); 177 | Logger::LogInfo("[Muxer::Finish] " + Logger::tr("Finishing encoders ...")); 178 | for(unsigned int i = 0; i < m_format_context->nb_streams; ++i) { 179 | assert(m_encoders[i] != NULL); 180 | m_encoders[i]->Finish(); // no deadlock: nothing in Muxer is locked in this thread (and BaseEncoder::Finish is lock-free, but that could change) 181 | } 182 | } 183 | 184 | double Muxer::GetActualBitRate() { 185 | SharedLock lock(&m_shared_data); 186 | return lock->m_stats_actual_bit_rate; 187 | } 188 | 189 | uint64_t Muxer::GetTotalBytes() { 190 | SharedLock lock(&m_shared_data); 191 | return lock->m_total_bytes; 192 | } 193 | 194 | void Muxer::EndStream(unsigned int stream_index) { 195 | assert(stream_index < m_format_context->nb_streams); 196 | StreamLock lock(&m_stream_data[stream_index]); 197 | lock->m_is_done = true; 198 | } 199 | 200 | void Muxer::AddPacket(unsigned int stream_index, AVPacket* packet) { 201 | assert(m_started); 202 | assert(stream_index < m_format_context->nb_streams); 203 | StreamLock lock(&m_stream_data[stream_index]); 204 | lock->m_packet_queue.push_back(packet); 205 | } 206 | 207 | unsigned int Muxer::GetQueuedPacketCount(unsigned int stream_index) { 208 | assert(m_started); 209 | assert(stream_index < m_format_context->nb_streams); 210 | StreamLock lock(&m_stream_data[stream_index]); 211 | return lock->m_packet_queue.size(); 212 | } 213 | 214 | void Muxer::Init() { 215 | 216 | // get the format we want (this is just a pointer, we don't have to free this) 217 | AVOutputFormat *format = av_guess_format(m_container_name.toUtf8().constData(), NULL, NULL); 218 | if(format == NULL) { 219 | Logger::LogError("[Muxer::Init] " + Logger::tr("Error: Can't find chosen output format!")); 220 | throw LibavException(); 221 | } 222 | 223 | Logger::LogInfo("[Muxer::Init] " + Logger::tr("Using format %1 (%2).").arg(format->name).arg(format->long_name)); 224 | 225 | // allocate format context 226 | // ffmpeg probably wants us to use avformat_alloc_output_context2 instead, but libav doesn't have it and I can't figure out why it's needed anyway 227 | m_format_context = avformat_alloc_context(); 228 | if(m_format_context == NULL) { 229 | Logger::LogError("[Muxer::Init] " + Logger::tr("Error: Can't allocate format context!")); 230 | throw LibavException(); 231 | } 232 | m_format_context->oformat = format; 233 | 234 | // open file 235 | if(avio_open(&m_format_context->pb, m_output_file.toLocal8Bit().constData(), AVIO_FLAG_WRITE) < 0) { 236 | Logger::LogError("[Muxer::Init] " + Logger::tr("Error: Can't open output file!")); 237 | throw LibavException(); 238 | } 239 | /*char sdp[2048]; 240 | av_sdp_create(&m_format_context,1, sdp, sizeof(sdp)); 241 | printf("%s\n",sdp); 242 | fflush(stdout); 243 | */ 244 | } 245 | 246 | void Muxer::Free() { 247 | if(m_format_context != NULL) { 248 | 249 | // write trailer (needed to free private muxer data) 250 | if(m_started) { 251 | if(av_write_trailer(m_format_context) != 0) { 252 | // we can't throw exceptions here because this is called from the destructor 253 | Logger::LogError("[Muxer::Free] " + Logger::tr("Error: Can't write trailer, continuing anyway.", "Don't translate 'trailer'")); 254 | } 255 | m_started = false; 256 | } 257 | 258 | // destroy the encoders 259 | for(unsigned int i = 0; i < m_format_context->nb_streams; ++i) { 260 | if(m_encoders[i] != NULL) { 261 | delete m_encoders[i]; // no deadlock: nothing in Muxer is locked in this thread 262 | m_encoders[i] = NULL; 263 | } 264 | } 265 | 266 | // close file 267 | if(m_format_context->pb != NULL) { 268 | avio_close(m_format_context->pb); 269 | m_format_context->pb = NULL; 270 | } 271 | 272 | // free everything 273 | #if SSR_USE_AVFORMAT_FREE_CONTEXT 274 | avformat_free_context(m_format_context); 275 | m_format_context = NULL; 276 | #else 277 | for(unsigned int i = 0; i < m_format_context->nb_streams; ++i) { 278 | av_freep(&m_format_context->streams[i]->codec); 279 | av_freep(&m_format_context->streams[i]); 280 | } 281 | av_free(m_format_context); 282 | m_format_context = NULL; 283 | #endif 284 | 285 | } 286 | } 287 | 288 | AVCodec* Muxer::FindCodec(const QString& codec_name) { 289 | AVCodec *codec = avcodec_find_encoder_by_name(codec_name.toUtf8().constData()); 290 | if(codec == NULL) { 291 | Logger::LogError("[Muxer::FindCodec] " + Logger::tr("Error: Can't find codec!")); 292 | throw LibavException(); 293 | } 294 | return codec; 295 | } 296 | 297 | AVStream* Muxer::AddStream(AVCodec* codec, AVCodecContext** codec_context) { 298 | assert(!m_started); 299 | assert(m_format_context->nb_streams < MUXER_MAX_STREAMS); 300 | 301 | Logger::LogInfo("[Muxer::AddStream] " + Logger::tr("Using codec %1 (%2).").arg(codec->name).arg(codec->long_name)); 302 | 303 | // create a new stream 304 | #if SSR_USE_AVSTREAM_CODECPAR 305 | AVStream *stream = avformat_new_stream(m_format_context, NULL); 306 | #elif SSR_USE_AVFORMAT_NEW_STREAM 307 | AVStream *stream = avformat_new_stream(m_format_context, codec); 308 | #else 309 | AVStream *stream = av_new_stream(m_format_context, m_format_context->nb_streams); 310 | #endif 311 | if(stream == NULL) { 312 | Logger::LogError("[Muxer::AddStream] " + Logger::tr("Error: Can't create new stream!")); 313 | throw LibavException(); 314 | } 315 | assert(stream->index == (int) m_format_context->nb_streams - 1); 316 | #if SSR_USE_AVSTREAM_CODECPAR 317 | *codec_context = avcodec_alloc_context3(codec); 318 | if(*codec_context == NULL) { 319 | Logger::LogError("[Muxer::AddStream] " + Logger::tr("Error: Can't create new codec context!")); 320 | throw LibavException(); 321 | } 322 | #else 323 | assert(stream->codec != NULL); 324 | *codec_context = stream->codec; 325 | #endif 326 | //stream->id = m_format_context->nb_streams - 1; 327 | 328 | #if !SSR_USE_AVFORMAT_NEW_STREAM 329 | // initialize the codec context (only needed for old API) 330 | if(avcodec_get_context_defaults3(*codec_context, codec) < 0) { 331 | Logger::LogError("[Muxer::AddStream] " + Logger::tr("Error: Can't get codec context defaults!")); 332 | throw LibavException(); 333 | } 334 | (*codec_context)->codec_id = codec->id; 335 | (*codec_context)->codec_type = codec->type; 336 | #endif 337 | 338 | // not sure why this is needed, but it's in the example code and it doesn't work without this 339 | if(m_format_context->oformat->flags & AVFMT_GLOBALHEADER) 340 | (*codec_context)->flags |= CODEC_FLAG_GLOBAL_HEADER; 341 | 342 | // if the codec is experimental, allow it 343 | if(codec->capabilities & CODEC_CAP_EXPERIMENTAL) { 344 | Logger::LogWarning("[Muxer::AddStream] " + Logger::tr("Warning: This codec is considered experimental by libav/ffmpeg.")); 345 | (*codec_context)->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; 346 | } 347 | 348 | #if SSR_USE_SIDE_DATA_ONLY_PACKETS && !SSR_USE_SIDE_DATA_ONLY_PACKETS_DEPRECATED 349 | // this option was added with the intent to deprecate it again in the next version, 350 | // because the ffmpeg/libav devs like deprecating things :) 351 | (*codec_context)->side_data_only_packets = 1; 352 | #endif 353 | 354 | return stream; 355 | } 356 | 357 | void Muxer::MuxerThread() { 358 | try { 359 | 360 | Logger::LogInfo("[Muxer::MuxerThread] " + Logger::tr("Muxer thread started.")); 361 | 362 | double total_time = 0.0; 363 | 364 | // start muxing 365 | while(!m_should_stop) { 366 | 367 | // get a packet from a stream that isn't done yet 368 | AVPacket *packet=NULL; 369 | unsigned int current_stream = 0, streams_done = 0; 370 | for(unsigned int i = 0; i < m_format_context->nb_streams; ++i) { 371 | StreamLock lock(&m_stream_data[i]); 372 | if(lock->m_packet_queue.empty()) { 373 | if(lock->m_is_done) 374 | ++streams_done; 375 | } else { 376 | current_stream = i; 377 | packet = lock->m_packet_queue.front(); 378 | lock->m_packet_queue.pop_front(); 379 | break; 380 | } 381 | } 382 | 383 | // if all streams are done, we can stop 384 | if(streams_done == m_format_context->nb_streams) { 385 | break; 386 | } 387 | 388 | // if there is no packet, wait and try again later 389 | if(packet == NULL) { 390 | usleep(20000); 391 | continue; 392 | } 393 | 394 | // try to figure out the time (the exact value is not critical, it's only used for bitrate statistics) 395 | AVStream *stream = m_encoders[current_stream]->GetStream(); 396 | AVCodecContext *codec_context = m_encoders[current_stream]->GetCodecContext(); 397 | double packet_time = 0.0; 398 | if(packet->dts != (int64_t) AV_NOPTS_VALUE) 399 | packet_time = (double) packet->dts * ToDouble(codec_context->time_base); 400 | else if(packet->pts != (int64_t) AV_NOPTS_VALUE) 401 | packet_time = (double) packet->pts * ToDouble(codec_context->time_base); 402 | if(packet_time > total_time) 403 | total_time = packet_time; 404 | 405 | // prepare packet 406 | packet->stream_index = current_stream; 407 | #if SSR_USE_AV_PACKET_RESCALE_TS 408 | av_packet_rescale_ts(packet, codec_context->time_base, stream->time_base); 409 | #else 410 | if(packet->pts != (int64_t) AV_NOPTS_VALUE) { 411 | packet->pts = av_rescale_q(packet->pts, codec_context->time_base, stream->time_base); 412 | } 413 | if(packet->dts != (int64_t) AV_NOPTS_VALUE) { 414 | packet->dts = av_rescale_q(packet->dts, codec_context->time_base, stream->time_base); 415 | } 416 | #endif 417 | 418 | // write the packet (again, why does libav/ffmpeg call this a frame?) 419 | if(av_interleaved_write_frame(m_format_context, packet) != 0) { 420 | Logger::LogError("[Muxer::MuxerThread] " + Logger::tr("Error: Can't write frame to muxer!")); 421 | throw LibavException(); 422 | } 423 | Free_avpacket(packet); 424 | 425 | // the data is now owned by libav/ffmpeg, so don't free it 426 | //packet->SetFreeOnDestruct(false); 427 | 428 | // update the byte counter 429 | { 430 | SharedLock lock(&m_shared_data); 431 | lock->m_total_bytes = m_format_context->pb->pos + (m_format_context->pb->buf_ptr - m_format_context->pb->buffer); 432 | if(lock->m_stats_previous_time == NOPTS_DOUBLE) { 433 | lock->m_stats_previous_time = total_time; 434 | lock->m_stats_previous_bytes = lock->m_total_bytes; 435 | } 436 | double timedelta = total_time - lock->m_stats_previous_time; 437 | if(timedelta > 0.999999) { 438 | lock->m_stats_actual_bit_rate = (double) ((lock->m_total_bytes - lock->m_stats_previous_bytes) * 8) / timedelta; 439 | lock->m_stats_previous_time = total_time; 440 | lock->m_stats_previous_bytes = lock->m_total_bytes; 441 | } 442 | } 443 | 444 | } 445 | 446 | // tell the others that we're done 447 | m_is_done = true; 448 | 449 | Logger::LogInfo("[Muxer::MuxerThread] " + Logger::tr("Muxer thread stopped.")); 450 | 451 | } catch(const std::exception& e) { 452 | m_error_occurred = true; 453 | Logger::LogError("[Muxer::MuxerThread] " + Logger::tr("Exception '%1' in muxer thread.").arg(e.what())); 454 | } catch(...) { 455 | m_error_occurred = true; 456 | Logger::LogError("[Muxer::MuxerThread] " + Logger::tr("Unknown exception in muxer thread.")); 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /AV/Output/Muxer.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2016 Maarten Baert 3 | 4 | This file is part of SimpleScreenRecorder. 5 | 6 | SimpleScreenRecorder is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | SimpleScreenRecorder is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with SimpleScreenRecorder. If not, see . 18 | */ 19 | 20 | #pragma once 21 | #include "global.h" 22 | 23 | #include "MutexDataPair.h" 24 | 25 | #define MUXER_MAX_STREAMS 2 26 | 27 | class AVPacketWrapper; 28 | class BaseEncoder; 29 | class VideoEncoder; 30 | class AudioEncoder; 31 | 32 | class Muxer { 33 | 34 | private: 35 | struct StreamData { 36 | std::deque m_packet_queue; 37 | bool m_is_done; 38 | }; 39 | typedef MutexDataPair::Lock StreamLock; 40 | struct SharedData { 41 | uint64_t m_total_bytes; 42 | double m_stats_actual_bit_rate; 43 | double m_stats_previous_time; 44 | uint64_t m_stats_previous_bytes; 45 | }; 46 | typedef MutexDataPair::Lock SharedLock; 47 | 48 | static constexpr unsigned int INVALID_STREAM = std::numeric_limits::max(); 49 | static constexpr double NOPTS_DOUBLE = -std::numeric_limits::max(); 50 | 51 | private: 52 | QString m_container_name, m_output_file; 53 | 54 | AVFormatContext *m_format_context; 55 | bool m_started; 56 | BaseEncoder *m_encoders[MUXER_MAX_STREAMS]; 57 | 58 | bool m_should_stop; 59 | std::thread m_thread; 60 | MutexDataPair m_stream_data[MUXER_MAX_STREAMS]; 61 | MutexDataPair m_shared_data; 62 | std::atomic m_is_done, m_error_occurred; 63 | 64 | public: 65 | Muxer(const QString& container_name, const QString& output_file); 66 | ~Muxer(); 67 | 68 | // Adds a video or audio encoder. 69 | VideoEncoder* AddVideoEncoder(const QString& codec_name, const std::vector >& codec_options, unsigned int bit_rate, 70 | unsigned int width, unsigned int height, unsigned int frame_rate); 71 | AudioEncoder* AddAudioEncoder(const QString& codec_name, const std::vector >& codec_options, unsigned int bit_rate, 72 | unsigned int channels, unsigned int sample_rate); 73 | 74 | // Starts the muxer. You can't create new encoders after calling this function. 75 | void Start(); 76 | 77 | // Tells the muxer to stop. It can take some time before the muxer really stops. 78 | void Finish(); 79 | 80 | // Returns the bit rate of the output stream. 81 | // This function is thread-safe. 82 | double GetActualBitRate(); 83 | 84 | // Returns the total number of bytes written to the output file. 85 | // This function is thread-safe. 86 | uint64_t GetTotalBytes(); 87 | 88 | // Returns whether the muxing is done. If this returns true, the object can be deleted. 89 | // Note: If an error occurred in the mixing thread, this function will return false. 90 | // This function is thread-safe and lock-free. 91 | inline bool IsDone() { return m_is_done; } 92 | 93 | // Returns whether an error has occurred in the input thread. 94 | // This function is thread-safe and lock-free. 95 | inline bool HasErrorOccurred() { return m_error_occurred; } 96 | 97 | public: 98 | inline QString GetOutputFile() { return m_output_file; } 99 | 100 | public: // internal 101 | 102 | // Ends the stream (i.e. tells the muxer that it shouldn't wait for more packets). Called by the encoder. 103 | // This function is thread-safe. 104 | void EndStream(unsigned int stream_index); 105 | 106 | // Adds a packet to the packet queue of a stream. Called by the encoder. 107 | // This function is thread-safe. 108 | void AddPacket(unsigned int stream_index, AVPacket *packet); 109 | 110 | // Returns the total number of packets in the queue of a stream. Called by the encoder. 111 | // This function is thread-safe. 112 | unsigned int GetQueuedPacketCount(unsigned int stream_index); 113 | 114 | private: 115 | void Init(); 116 | void Free(); 117 | 118 | AVCodec* FindCodec(const QString& codec_name); 119 | AVStream* AddStream(AVCodec* codec, AVCodecContext** codec_context); 120 | 121 | void MuxerThread(); 122 | 123 | }; 124 | -------------------------------------------------------------------------------- /AV/Output/OutputSettings.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2016 Maarten Baert 3 | 4 | This file is part of SimpleScreenRecorder. 5 | 6 | SimpleScreenRecorder is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | SimpleScreenRecorder is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with SimpleScreenRecorder. If not, see . 18 | */ 19 | 20 | #pragma once 21 | #include "global.h" 22 | 23 | struct OutputSettings { 24 | 25 | QString file; 26 | QString container_avname; 27 | 28 | QString video_codec_avname; 29 | unsigned int video_kbit_rate; 30 | std::vector > video_options; 31 | unsigned int video_width, video_height; 32 | unsigned int video_frame_rate; 33 | bool video_allow_frame_skipping; 34 | 35 | QString audio_codec_avname; 36 | unsigned int audio_kbit_rate; 37 | std::vector > audio_options; 38 | unsigned int audio_channels, audio_sample_rate; 39 | 40 | }; 41 | 42 | struct OutputFormat { 43 | 44 | bool m_video_enabled; 45 | unsigned int m_video_width, m_video_height; 46 | unsigned int m_video_frame_rate; 47 | AVPixelFormat m_video_pixel_format; 48 | 49 | bool m_audio_enabled; 50 | unsigned int m_audio_channels, m_audio_sample_rate; 51 | unsigned int m_audio_frame_size; 52 | AVSampleFormat m_audio_sample_format; 53 | 54 | }; 55 | -------------------------------------------------------------------------------- /AV/Output/VideoEncoder.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2016 Maarten Baert 3 | 4 | This file is part of SimpleScreenRecorder. 5 | 6 | SimpleScreenRecorder is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | SimpleScreenRecorder is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with SimpleScreenRecorder. If not, see . 18 | */ 19 | 20 | #include "global.h" 21 | #include "VideoEncoder.h" 22 | 23 | #include "Logger.h" 24 | //#include "AVWrapper.h" 25 | #include "Muxer.h" 26 | #include "X264Presets.h" 27 | 28 | const std::vector VideoEncoder::SUPPORTED_PIXEL_FORMATS = { 29 | {"nv12", AV_PIX_FMT_NV12, true}, 30 | {"yuv420", AV_PIX_FMT_YUV420P, true}, 31 | {"yuv422", AV_PIX_FMT_YUV422P, true}, 32 | {"yuv444", AV_PIX_FMT_YUV444P, true}, 33 | {"bgra", AV_PIX_FMT_BGRA, false}, 34 | {"bgr", AV_PIX_FMT_BGR24, false}, 35 | }; 36 | 37 | bool AVCodecSupportsPixelFormat(const AVCodec* codec, AVPixelFormat pixel_fmt) { 38 | const AVPixelFormat *p = codec->pix_fmts; 39 | if(p == NULL) 40 | return true; // NULL means 'unknown' or 'any', assume it is supported 41 | while(*p != AV_PIX_FMT_NONE) { 42 | if(*p == pixel_fmt) 43 | return true; 44 | ++p; 45 | } 46 | return false; 47 | } 48 | 49 | VideoEncoder::VideoEncoder(Muxer* muxer, AVStream* stream, AVCodecContext* codec_context, AVCodec* codec, AVDictionary** options) 50 | : BaseEncoder(muxer, stream, codec_context, codec, options) { 51 | 52 | #if !SSR_USE_AVCODEC_ENCODE_VIDEO2 53 | // allocate a temporary buffer 54 | // Apparently libav/ffmpeg completely ignores the size of the buffer, and if it's too small it just crashes. 55 | // Originally it was 256k, which is large enough for about 99.9% of the packets, but it still occasionally crashes. 56 | // So now I'm using a buffer that's always at least large enough to hold a 256k header and *two* completely uncompressed frames. 57 | // (one YUV frame takes w * h * 1.5 bytes) 58 | // Newer versions of libav/ffmpeg have deprecated avcodec_encode_video and added a new function which does the allocation 59 | // automatically, just like avcodec_encode_audio2, but that function isn't available in Ubuntu 12.04/12.10 yet. 60 | m_temp_buffer.resize(std::max(FF_MIN_BUFFER_SIZE, 256 * 1024 + GetCodecContext()->width * GetCodecContext()->height * 3)); 61 | #endif 62 | //domon: stop the encode to move other place 63 | //StartThread(); 64 | } 65 | 66 | VideoEncoder::~VideoEncoder() { 67 | StopThread(); 68 | } 69 | 70 | AVPixelFormat VideoEncoder::GetPixelFormat() { 71 | return GetCodecContext()->pix_fmt; 72 | } 73 | 74 | unsigned int VideoEncoder::GetWidth() { 75 | return GetCodecContext()->width; 76 | } 77 | 78 | unsigned int VideoEncoder::GetHeight() { 79 | return GetCodecContext()->height; 80 | } 81 | 82 | unsigned int VideoEncoder::GetFrameRate() { 83 | assert(GetCodecContext()->time_base.num == 1); 84 | return GetCodecContext()->time_base.den; 85 | } 86 | 87 | bool VideoEncoder::AVCodecIsSupported(const QString& codec_name) { 88 | AVCodec *codec = avcodec_find_encoder_by_name(codec_name.toUtf8().constData()); 89 | if(codec == NULL) 90 | return false; 91 | if(!av_codec_is_encoder(codec)) 92 | return false; 93 | if(codec->type != AVMEDIA_TYPE_VIDEO) 94 | return false; 95 | for(unsigned int i = 0; i < SUPPORTED_PIXEL_FORMATS.size(); ++i) { 96 | if(AVCodecSupportsPixelFormat(codec, SUPPORTED_PIXEL_FORMATS[i].m_format)) 97 | return true; 98 | } 99 | return false; 100 | } 101 | 102 | void VideoEncoder::PrepareStream(AVStream* stream, AVCodecContext* codec_context, AVCodec* codec, AVDictionary** options, const std::vector >& codec_options, 103 | unsigned int bit_rate, unsigned int width, unsigned int height, unsigned int frame_rate) { 104 | 105 | if(width == 0 || height == 0) { 106 | Logger::LogError("[VideoEncoder::PrepareStream] " + Logger::tr("Error: Width or height is zero!")); 107 | throw LibavException(); 108 | } 109 | if(width > 10000 || height > 10000) { 110 | Logger::LogError("[VideoEncoder::PrepareStream] " + Logger::tr("Error: Width or height is too large, the maximum width and height is %1!").arg(10000)); 111 | throw LibavException(); 112 | } 113 | if(width % 2 != 0 || height % 2 != 0) { 114 | Logger::LogError("[VideoEncoder::PrepareStream] " + Logger::tr("Error: Width or height is not an even number!")); 115 | throw LibavException(); 116 | } 117 | if(frame_rate == 0) { 118 | Logger::LogError("[VideoEncoder::PrepareStream] " + Logger::tr("Error: Frame rate is zero!")); 119 | throw LibavException(); 120 | } 121 | 122 | // initialize codec context 123 | codec_context->bit_rate = bit_rate; 124 | codec_context->width = width; 125 | codec_context->height = height; 126 | codec_context->time_base.num = 1; 127 | codec_context->time_base.den = frame_rate; 128 | #if SSR_USE_AVSTREAM_TIME_BASE 129 | stream->time_base = codec_context->time_base; 130 | #endif 131 | codec_context->sample_aspect_ratio.num = 1; 132 | codec_context->sample_aspect_ratio.den = 1; 133 | stream->sample_aspect_ratio = codec_context->sample_aspect_ratio; 134 | codec_context->thread_count = std::max(1, (int) std::thread::hardware_concurrency()); 135 | 136 | // parse options 137 | QString pixel_format_name; 138 | for(unsigned int i = 0; i < codec_options.size(); ++i) { 139 | const QString &key = codec_options[i].first, &value = codec_options[i].second; 140 | if(key == "threads") { 141 | codec_context->thread_count = ParseCodecOptionInt(key, value, 1, 100); 142 | } else if(key == "qscale") { 143 | codec_context->flags |= CODEC_FLAG_QSCALE; 144 | codec_context->global_quality = lrint(ParseCodecOptionDouble(key, value, -1.0e6, 1.0e6, FF_QP2LAMBDA)); 145 | } else if(key == "minrate") { 146 | codec_context->rc_min_rate = ParseCodecOptionInt(key, value, 1, 1000000, 1024); // kbps 147 | } else if(key == "maxrate") { 148 | codec_context->rc_max_rate = ParseCodecOptionInt(key, value, 1, 1000000, 1024); // kbps 149 | } else if(key == "bufsize") { 150 | codec_context->rc_buffer_size = ParseCodecOptionInt(key, value, 1, 1000000, 1024); // kbps 151 | } else if(key == "keyint") { 152 | codec_context->gop_size = ParseCodecOptionInt(key, value, 1, 1000000); 153 | } else if(key == "pixelformat") { 154 | pixel_format_name = value; 155 | #if !SSR_USE_AVCODEC_PRIVATE_PRESET 156 | } else if(key == "crf") { 157 | codec_context->crf = ParseCodecOptionInt(key, value, 0, 51); 158 | #endif 159 | #if !SSR_USE_AVCODEC_PRIVATE_PRESET 160 | } else if(key == "preset") { 161 | X264Preset(codec_context, value.toUtf8().constData()); 162 | #endif 163 | } else { 164 | av_dict_set(options, key.toUtf8().constData(), value.toUtf8().constData(), 0); 165 | } 166 | } 167 | 168 | // choose the pixel format 169 | codec_context->pix_fmt = AV_PIX_FMT_NONE; 170 | for(unsigned int i = 0; i < SUPPORTED_PIXEL_FORMATS.size(); ++i) { 171 | if(!pixel_format_name.isEmpty() && pixel_format_name != SUPPORTED_PIXEL_FORMATS[i].m_name) 172 | continue; 173 | if(!AVCodecSupportsPixelFormat(codec, SUPPORTED_PIXEL_FORMATS[i].m_format)) 174 | continue; 175 | Logger::LogInfo("[VideoEncoder::PrepareStream] " + Logger::tr("Using pixel format %1.").arg(SUPPORTED_PIXEL_FORMATS[i].m_name)); 176 | codec_context->pix_fmt = SUPPORTED_PIXEL_FORMATS[i].m_format; 177 | if(SUPPORTED_PIXEL_FORMATS[i].m_is_yuv) { 178 | codec_context->color_primaries = AVCOL_PRI_BT709; 179 | codec_context->color_trc = AVCOL_TRC_BT709; 180 | codec_context->colorspace = AVCOL_SPC_BT709; 181 | codec_context->color_range = AVCOL_RANGE_MPEG; 182 | codec_context->chroma_sample_location = AVCHROMA_LOC_CENTER; 183 | } else { 184 | codec_context->colorspace = AVCOL_SPC_RGB; 185 | } 186 | break; 187 | } 188 | if(codec_context->pix_fmt == AV_PIX_FMT_NONE) { 189 | Logger::LogError("[VideoEncoder::PrepareStream] " + Logger::tr("Error: The pixel format is not supported by the codec!")); 190 | throw LibavException(); 191 | } 192 | 193 | } 194 | 195 | bool VideoEncoder::EncodeFrame(AVFrame* frame) { 196 | 197 | if(frame != NULL) { 198 | #if SSR_USE_AVFRAME_WIDTH_HEIGHT 199 | assert(frame->width == GetCodecContext()->width); 200 | assert(frame->height == GetCodecContext()->height); 201 | #endif 202 | #if SSR_USE_AVFRAME_FORMAT 203 | assert(frame->format == GetCodecContext()->pix_fmt); 204 | #endif 205 | #if SSR_USE_AVFRAME_SAR 206 | assert(frame->sample_aspect_ratio.num == GetCodecContext()->sample_aspect_ratio.num); 207 | assert(frame->sample_aspect_ratio.den == GetCodecContext()->sample_aspect_ratio.den); 208 | #endif 209 | } 210 | 211 | #if SSR_USE_AVCODEC_SEND_RECEIVE 212 | // send a frame 213 | AVFrame *avframe = (frame == NULL)? NULL : frame; 214 | try { 215 | if(avcodec_send_frame(GetCodecContext(), avframe) < 0) { 216 | Logger::LogError("[VideoEncoder::EncodeFrame] " + Logger::tr("Error: Sending of video frame failed!")); 217 | throw LibavException(); 218 | } 219 | } catch(...) { 220 | av_frame_free(&avframe); 221 | throw; 222 | } 223 | //domon: free avframe data and itself 224 | av_free(frame->data[0]); 225 | av_frame_free(&avframe); 226 | 227 | // try to receive a packet 228 | for( ; ; ) { 229 | #if SSR_USE_AV_PACKET_ALLOC 230 | AVPacket *packet = av_packet_alloc(); 231 | if(packet == NULL) 232 | std::bad_alloc(); 233 | #else 234 | packet = new AVPacket; 235 | av_init_packet(packet); 236 | packet->data = NULL; 237 | packet->size = 0; 238 | #endif 239 | int res = avcodec_receive_packet(GetCodecContext(), packet); 240 | if(res == 0) { // we have a packet, send the packet to the muxer 241 | GetMuxer()->AddPacket(GetStream()->index, packet); 242 | IncrementPacketCounter(); 243 | } else if(res == AVERROR(EAGAIN)) { // we have no packet 244 | return true; 245 | } else if(res == AVERROR_EOF) { // this is the end of the stream 246 | return false; 247 | } else { 248 | Logger::LogError("[VideoEncoder::EncodeFrame] " + Logger::tr("Error: Receiving of video packet failed!")); 249 | throw LibavException(); 250 | } 251 | } 252 | #elif SSR_USE_AVCODEC_ENCODE_VIDEO2 253 | 254 | // allocate a packet 255 | #if SSR_USE_AV_PACKET_ALLOC 256 | AVPacket *packet = av_packet_alloc(); 257 | if(packet == NULL) 258 | std::bad_alloc(); 259 | #else 260 | packet = new AVPacket; 261 | av_init_packet(packet); 262 | packet->data = NULL; 263 | packet->size = 0; 264 | #endif 265 | 266 | // encode the frame 267 | int got_packet; 268 | if(avcodec_encode_video2(GetCodecContext(), packet, (frame == NULL)? NULL : frame, &got_packet) < 0) { 269 | Logger::LogError("[VideoEncoder::EncodeFrame] " + Logger::tr("Error: Encoding of video frame failed!")); 270 | throw LibavException(); 271 | } 272 | 273 | // do we have a packet? 274 | if(got_packet) { 275 | 276 | // send the packet to the muxer 277 | GetMuxer()->AddPacket(GetStream()->index, packet); 278 | IncrementPacketCounter(); 279 | return true; 280 | 281 | } else { 282 | return false; 283 | } 284 | 285 | #else 286 | 287 | // encode the frame 288 | int bytes_encoded = avcodec_encode_video(GetCodecContext(), m_temp_buffer.data(), m_temp_buffer.size(), frame); 289 | if(bytes_encoded < 0) { 290 | Logger::LogError("[VideoEncoder::EncodeFrame] " + Logger::tr("Error: Encoding of video frame failed!")); 291 | throw LibavException(); 292 | } 293 | 294 | // do we have a packet? 295 | if(bytes_encoded > 0) { 296 | 297 | // allocate a packet 298 | std::unique_ptr packet(new AVPacketWrapper(bytes_encoded)); 299 | 300 | // copy the data 301 | memcpy(packet->GetPacket()->data, m_temp_buffer.data(), bytes_encoded); 302 | 303 | // set the timestamp 304 | // note: pts will be rescaled and stream_index will be set by Muxer 305 | if(GetCodecContext()->coded_frame != NULL && GetCodecContext()->coded_frame->pts != (int64_t) AV_NOPTS_VALUE) 306 | packet->GetPacket()->pts = GetCodecContext()->coded_frame->pts; 307 | 308 | // set the keyframe flag 309 | if(GetCodecContext()->coded_frame->key_frame) 310 | packet->GetPacket()->flags |= AV_PKT_FLAG_KEY; 311 | 312 | // send the packet to the muxer 313 | GetMuxer()->AddPacket(GetStream()->index, std::move(packet)); 314 | IncrementPacketCounter(); 315 | return true; 316 | 317 | } else { 318 | return false; 319 | } 320 | 321 | #endif 322 | 323 | } 324 | -------------------------------------------------------------------------------- /AV/Output/VideoEncoder.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2016 Maarten Baert 3 | 4 | This file is part of SimpleScreenRecorder. 5 | 6 | SimpleScreenRecorder is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | SimpleScreenRecorder is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with SimpleScreenRecorder. If not, see . 18 | */ 19 | 20 | #pragma once 21 | #include "global.h" 22 | 23 | #include "BaseEncoder.h" 24 | 25 | class VideoEncoder : public BaseEncoder { 26 | 27 | private: 28 | struct PixelFormatData { 29 | QString m_name; 30 | AVPixelFormat m_format; 31 | bool m_is_yuv; 32 | }; 33 | 34 | private: 35 | static const std::vector SUPPORTED_PIXEL_FORMATS; 36 | 37 | private: 38 | #if !SSR_USE_AVCODEC_ENCODE_VIDEO2 39 | std::vector m_temp_buffer; 40 | #endif 41 | 42 | public: 43 | VideoEncoder(Muxer* muxer, AVStream* stream, AVCodecContext* codec_context, AVCodec* codec, AVDictionary** options); 44 | ~VideoEncoder(); 45 | 46 | // Returns the required pixel format. 47 | AVPixelFormat GetPixelFormat(); 48 | 49 | unsigned int GetWidth(); 50 | unsigned int GetHeight(); 51 | unsigned int GetFrameRate(); 52 | 53 | public: 54 | static bool AVCodecIsSupported(const QString& codec_name); 55 | static void PrepareStream(AVStream* stream, AVCodecContext* codec_context, AVCodec* codec, AVDictionary** options, const std::vector >& codec_options, 56 | unsigned int bit_rate, unsigned int width, unsigned int height, unsigned int frame_rate); 57 | 58 | virtual bool EncodeFrame(AVFrame* frame) override; 59 | 60 | }; 61 | -------------------------------------------------------------------------------- /Logger.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2016 Maarten Baert 3 | 4 | This file is part of SimpleScreenRecorder. 5 | 6 | SimpleScreenRecorder is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | SimpleScreenRecorder is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with SimpleScreenRecorder. If not, see . 18 | */ 19 | 20 | #include "global.h" 21 | #include "Logger.h" 22 | 23 | //#include "Main.h" 24 | 25 | Logger *Logger::s_instance = NULL; 26 | 27 | Logger::Logger() { 28 | assert(s_instance == NULL); 29 | qRegisterMetaType(); 30 | s_instance = this; 31 | } 32 | 33 | Logger::~Logger() { 34 | assert(s_instance == this); 35 | s_instance = NULL; 36 | } 37 | 38 | void Logger::LogInfo(const QString& str) { 39 | assert(s_instance != NULL); 40 | std::lock_guard lock(s_instance->m_mutex); Q_UNUSED(lock); 41 | fprintf(stderr, "%s\n", str.toLocal8Bit().constData()); 42 | emit s_instance->NewLine(TYPE_INFO, str); 43 | } 44 | 45 | void Logger::LogWarning(const QString& str) { 46 | assert(s_instance != NULL); 47 | std::lock_guard lock(s_instance->m_mutex); Q_UNUSED(lock); 48 | fprintf(stderr, "\033[1;33m%s\033[0m\n", str.toLocal8Bit().constData()); 49 | emit s_instance->NewLine(TYPE_WARNING, str); 50 | } 51 | 52 | void Logger::LogError(const QString& str) { 53 | assert(s_instance != NULL); 54 | std::lock_guard lock(s_instance->m_mutex); Q_UNUSED(lock); 55 | fprintf(stderr, "\033[1;31m%s\033[0m\n", str.toLocal8Bit().constData()); 56 | emit s_instance->NewLine(TYPE_ERROR, str); 57 | } 58 | -------------------------------------------------------------------------------- /Logger.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2016 Maarten Baert 3 | 4 | This file is part of SimpleScreenRecorder. 5 | 6 | SimpleScreenRecorder is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | SimpleScreenRecorder is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with SimpleScreenRecorder. If not, see . 18 | */ 19 | 20 | #pragma once 21 | #include "global.h" 22 | 23 | class Logger : public QObject { 24 | Q_OBJECT 25 | 26 | public: 27 | enum enum_type { 28 | TYPE_INFO, 29 | TYPE_WARNING, 30 | TYPE_ERROR 31 | }; 32 | 33 | private: 34 | std::mutex m_mutex; 35 | 36 | static Logger *s_instance; 37 | 38 | public: 39 | Logger(); 40 | ~Logger(); 41 | 42 | // These functions are thread-safe. 43 | static void LogInfo(const QString& str); 44 | static void LogWarning(const QString& str); 45 | static void LogError(const QString& str); 46 | 47 | inline static Logger* GetInstance() { assert(s_instance != NULL); return s_instance; } 48 | 49 | signals: 50 | void NewLine(Logger::enum_type type, QString str); 51 | 52 | }; 53 | 54 | Q_DECLARE_METATYPE(Logger::enum_type) 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Screen_Record_Demo 2 | ### 这是一个linux环境下的基于QT的截屏及屏幕录像的测试Demo,大部分内容截取自SSR(SimpleScreenRecorder)项目 3 | 4 | 1.基于QT,FFMPEG,Xlib 相关开源库 5 | 6 | 其中Xlib用于获取屏幕原始图像,FFMPEG负责编码成帧,QT负责预览与测试,Xlib一般系统自带,FFMPEG与QT需自行安装 7 | 8 | 2.可截取任意大小屏幕区域或任意一个窗口 9 | 10 | 这个Demo主要用于想在GUI应用中添加截屏和录像功能时,做一个简单的参考 11 | 12 | 3.SSR项目非常的棒,其功能完善,支持各种音视频输入,以及各种编码的配置 13 | 14 | SSR源码内容很多,音视频相关工作者可以参考,这里只截取了一点视频编码相关的内容 15 | 16 | 4.内容相对粗糙简单,测试功能流程之用,不包含音频部分,不合理的地方请忽略 17 | 18 | ---此屏幕录像Demo产生的视频文件见 test.mkv--- 19 | -------------------------------------------------------------------------------- /TempBuffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2016 Maarten Baert 3 | 4 | This file is part of SimpleScreenRecorder. 5 | 6 | SimpleScreenRecorder is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | SimpleScreenRecorder is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with SimpleScreenRecorder. If not, see . 18 | */ 19 | 20 | #pragma once 21 | #include "global.h" 22 | 23 | // This is a simple class that allocates temporary buffers efficiently by reusing memory to avoid new memory allocations. 24 | // It is more efficient than std::vector because it doesn't copy data or initialize it to zero. 25 | // The amount of allocated memory will only grow, not shrink (i.e. like std::vector). 26 | // The 'GetSize' function will always report the actual size, which can be larger than the requested size. 27 | 28 | #define TEMPBUFFER_ALIGN 16 29 | 30 | template 31 | class TempBuffer { 32 | 33 | private: 34 | T *m_data; 35 | size_t m_size; 36 | 37 | public: 38 | inline TempBuffer() { 39 | m_data = NULL; 40 | m_size = 0; 41 | } 42 | inline ~TempBuffer() { 43 | free(m_data); 44 | } 45 | inline void Alloc(size_t size, bool copy = false) { 46 | if(size > m_size) { 47 | if(m_size != 0) 48 | size += size / 4; 49 | void *temp; 50 | if(posix_memalign(&temp, TEMPBUFFER_ALIGN, sizeof(T) * size) != 0) 51 | throw std::bad_alloc(); 52 | if(copy) 53 | memcpy(temp, m_data, sizeof(T) * m_size); 54 | free(m_data); 55 | m_data = (T*) temp; 56 | m_size = size; 57 | } 58 | } 59 | 60 | public: 61 | inline T* GetData() { return m_data; } 62 | inline size_t GetSize() { return m_size; } 63 | inline T& operator[](size_t i) { return m_data[i]; } 64 | 65 | // noncopyable 66 | TempBuffer(const TempBuffer&) = delete; 67 | TempBuffer& operator=(const TempBuffer&) = delete; 68 | 69 | }; 70 | -------------------------------------------------------------------------------- /global.h: -------------------------------------------------------------------------------- 1 | #ifndef GLOBAL_H 2 | #define GLOBAL_H 3 | 4 | #include 5 | 6 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #endif 42 | 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | 66 | #include 67 | #include 68 | #include 69 | 70 | extern "C" { 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | #include 77 | } 78 | 79 | // Whether x86/x64 specific instructions should be used. 80 | #ifndef SSR_USE_X86_ASM 81 | #error SSR_USE_X86_ASM should be defined! 82 | #endif 83 | 84 | // Whether ffmpeg or libav version numbers should be used for tests. The ffmpeg version number is usually slightly higher, 85 | // so that's the default (deprecation warnings are better than compilation errors). 86 | #ifndef SSR_USE_FFMPEG_VERSIONS 87 | #error SSR_USE_FFMPEG_VERSIONS should be defined! 88 | #endif 89 | 90 | // generic macro to test version numbers 91 | #define TEST_MAJOR_MINOR(major, minor, required_major, required_minor) (major > required_major || (major == required_major && minor >= required_minor)) 92 | 93 | // test GCC version 94 | #define TEST_GCC_VERSION(major, minor) TEST_MAJOR_MINOR(__GNUC__, __GNUC_MINOR__, major, minor) 95 | 96 | // 'override' was added in GCC 4.7 97 | #if !TEST_GCC_VERSION(4, 7) 98 | #define override 99 | #endif 100 | 101 | // std::atomic_thread_fence exists in GCC 4.6 but it doesn't link properly for some reason 102 | #if !TEST_GCC_VERSION(4, 7) 103 | #define atomic_thread_fence atomic_thread_fence_replacement 104 | namespace std { 105 | inline void atomic_thread_fence_replacement(memory_order) { 106 | __sync_synchronize(); 107 | } 108 | } 109 | #endif 110 | 111 | // libav/ffmpeg API changes with version numbers are listed in their repositories in the file 'doc/APIchanges' 112 | // I recommend using the ffmpeg one: 113 | // http://git.videolan.org/?p=ffmpeg.git;a=blob;f=doc/APIchanges 114 | // This one lists version numbers for both ffmpeg and libav whereas libav just ignores ffmpeg. 115 | 116 | #if SSR_USE_FFMPEG_VERSIONS 117 | #define TEST_AV_VERSION(prefix, ffmpeg_major, ffmpeg_minor, libav_major, libav_minor) TEST_MAJOR_MINOR(prefix##_VERSION_MAJOR, prefix##_VERSION_MINOR, ffmpeg_major, ffmpeg_minor) 118 | #else 119 | #define TEST_AV_VERSION(prefix, ffmpeg_major, ffmpeg_minor, libav_major, libav_minor) TEST_MAJOR_MINOR(prefix##_VERSION_MAJOR, prefix##_VERSION_MINOR, libav_major, libav_minor) 120 | #endif 121 | 122 | // AVStream::codecpar: lavf 57.33.100 / 57.5.0 123 | #define SSR_USE_AVSTREAM_CODECPAR TEST_AV_VERSION(LIBAVFORMAT, 57, 33, 57, 5) 124 | // AVStream::time_base as time base hint: lavf 55.44.100 / 55.20.0 125 | #define SSR_USE_AVSTREAM_TIME_BASE TEST_AV_VERSION(LIBAVFORMAT, 55, 44, 55, 20) 126 | // avformat_network_init: lavf 53.19.0 / 53.13.0 127 | #define SSR_USE_AVFORMAT_NETWORK_INIT TEST_AV_VERSION(LIBAVFORMAT, 53, 19, 53, 13) 128 | // avformat_new_stream: lavf 53.17.0 / 53.10.0 129 | #define SSR_USE_AVFORMAT_NEW_STREAM TEST_AV_VERSION(LIBAVFORMAT, 53, 17, 53, 10) 130 | // avformat_query_codec: lavf 53.11.0 / 53.8.0 131 | #define SSR_USE_AVFORMAT_QUERY_CODEC TEST_AV_VERSION(LIBAVFORMAT, 53, 11, 53, 8) 132 | // avformat_free_context: lavf 52.96.0 / 52.96.0 133 | #define SSR_USE_AVFORMAT_FREE_CONTEXT TEST_AV_VERSION(LIBAVFORMAT, 52, 96, 52, 96) 134 | 135 | // avcodec_send_packet, avcodec_receive_frame, avcodec_send_frame, avcodec_receive_packet: lavc 57.37.100 / ??? 136 | #define SSR_USE_AVCODEC_SEND_RECEIVE TEST_AV_VERSION(LIBAVCODEC, 57, 37, 999, 999) 137 | // av_packet_alloc: lavc 57.12.100 / 57.8.0 138 | #define SSR_USE_AV_PACKET_ALLOC TEST_AV_VERSION(LIBAVCODEC, 57, 12, 57, 8) 139 | // av_packet_rescale_ts: lavc 55.68.100 / 55.55.0 140 | #define SSR_USE_AV_PACKET_RESCALE_TS TEST_AV_VERSION(LIBAVCODEC, 55, 68, 55, 55) 141 | // AVCodecContext::side_data_only_packets added: lavc 55.66.100 / 55.54.0 142 | #define SSR_USE_SIDE_DATA_ONLY_PACKETS TEST_AV_VERSION(LIBAVCODEC, 55, 66, 55, 54) 143 | // AVCodecContext::side_data_only_packets deprecated: lavc 57.2 / 57.2 144 | // - ffmpeg: missing, commit: https://git.videolan.org/?p=ffmpeg.git;a=commit;h=6064f697a321174232a3fad351afb21150c3e9e5 145 | // - libav: missing, commit: https://git.libav.org/?p=libav.git;a=commit;h=6064f697a321174232a3fad351afb21150c3e9e5 146 | #define SSR_USE_SIDE_DATA_ONLY_PACKETS_DEPRECATED TEST_AV_VERSION(LIBAVCODEC, 57, 2, 57, 2) 147 | // av_frame_alloc, av_frame_free: lavc 55.45.101 / 55.28.1 148 | #define SSR_USE_AV_FRAME_ALLOC TEST_AV_VERSION(LIBAVCODEC, 55, 45, 55, 28) 149 | #define SSR_USE_AV_FRAME_FREE SSR_USE_AV_FRAME_ALLOC 150 | // avcodec_free_frame: lavc 54.59.100 / 54.28.0 151 | #define SSR_USE_AVCODEC_FREE_FRAME TEST_AV_VERSION(LIBAVCODEC, 54, 59, 54, 28) 152 | // AV_CODEC_ID_* instead of CODEC_ID_*: lavc 54.51.100 / 54.25.0 153 | #define SSR_USE_AV_CODEC_ID TEST_AV_VERSION(LIBAVCODEC, 54, 51, 54, 25) 154 | // AVFrame::channels: lavc 54.46.100 / ??? 155 | #define SSR_USE_AVFRAME_CHANNELS TEST_AV_VERSION(LIBAVCODEC, 54, 46, 999, 999) 156 | // AVFrame::sample_rate: lavc 54.20.100 / 54.13.0 157 | #define SSR_USE_AVFRAME_SAMPLE_RATE TEST_AV_VERSION(LIBAVCODEC, 54, 20, 54, 13) 158 | // av_codec_is_encoder: lavc 54.8.100 / 54.7.0 159 | #define SSR_USE_AV_CODEC_IS_ENCODER TEST_AV_VERSION(LIBAVCODEC, 54, 8, 54, 7) 160 | // avcodec_encode_video2: lavc 54.2.100 / 54.1.0 161 | #define SSR_USE_AVCODEC_ENCODE_VIDEO2 TEST_AV_VERSION(LIBAVCODEC, 54, 2, 54, 1) 162 | // avcodec_encode_audio2: lavc 53.56.105 / 53.34.0 163 | #define SSR_USE_AVCODEC_ENCODE_AUDIO2 TEST_AV_VERSION(LIBAVCODEC, 53, 56, 53, 34) 164 | // AVFrame::nb_samples, AVFrame::extended_data and avcodec_decode_audio4: lavc 53.40.0 / 53.25.0 165 | #define SSR_USE_AVFRAME_NB_SAMPLES TEST_AV_VERSION(LIBAVCODEC, 53, 40, 53, 25) 166 | #define SSR_USE_AVFRAME_EXTENDED_DATA SSR_USE_AVFRAME_NB_SAMPLES 167 | #define SSR_USE_AVCODEC_DECODE_AUDIO4 SSR_USE_AVFRAME_NB_SAMPLES 168 | // the 'preset' private option: lavc 53.8 / 53.8 169 | // - ffmpeg: missing, commit: http://git.videolan.org/?p=ffmpeg.git;a=commit;h=07a227b432e49f4c0f35bbef48009f4d8438b32e 170 | // - libav: missing, commit: https://git.libav.org/?p=libav.git;a=commit;h=07a227b432e49f4c0f35bbef48009f4d8438b32e 171 | #define SSR_USE_AVCODEC_PRIVATE_PRESET TEST_AV_VERSION(LIBAVCODEC, 53, 8, 53, 8) 172 | // the 'crf' private option: lavc 53.8 / 53.8 173 | // - ffmpeg: missing, commit: http://git.videolan.org/?p=ffmpeg.git;a=commit;h=d5dc8cc2974c816ba964692b75c9f17f40830414 174 | // - libav: missing, commit: https://git.libav.org/?p=libav.git;a=commit;h=d5dc8cc2974c816ba964692b75c9f17f40830414 175 | #define SSR_USE_AVCODEC_PRIVATE_CRF TEST_AV_VERSION(LIBAVCODEC, 53, 8, 53, 8) 176 | // AVFrame::format: lavc 53.5.0 / 53.31.0 177 | #define SSR_USE_AVFRAME_FORMAT TEST_AV_VERSION(LIBAVCODEC, 53, 5, 53, 31) 178 | // AVFrame::width, AVFrame::height: lavc 53.4.0 / 53.31.0 179 | #define SSR_USE_AVFRAME_WIDTH_HEIGHT TEST_AV_VERSION(LIBAVCODEC, 53, 4, 53, 31) 180 | // AVFrame::sample_aspect_ratio: lavc 53.3.0 / 53.31.0 181 | #define SSR_USE_AVFRAME_SAR TEST_AV_VERSION(LIBAVCODEC, 53, 3, 53, 31) 182 | 183 | // AV_PIX_FMT_* instead of PIX_FMT_*: lavu 51.74.100 / 51.42.0 184 | #define SSR_USE_AV_PIX_FMT TEST_AV_VERSION(LIBAVUTIL, 51, 74, 51, 42) 185 | // planar sample formats: lavu 51.27.0 / 51.17.0 186 | #define SSR_USE_AVUTIL_PLANAR_SAMPLE_FMT TEST_AV_VERSION(LIBAVUTIL, 51, 27, 51, 17) 187 | 188 | // simple function to do 16-byte alignment 189 | inline size_t grow_align16(size_t size) { 190 | return (size_t) (size + 15) & ~((size_t) 15); 191 | } 192 | 193 | class X11Exception : public std::exception { 194 | public: 195 | inline virtual const char* what() const throw() override { 196 | return "X11Exception"; 197 | } 198 | }; 199 | 200 | class LibavException : public std::exception { 201 | public: 202 | inline virtual const char* what() const throw() override { 203 | return "LibavException"; 204 | } 205 | }; 206 | 207 | class ALSAException : public std::exception { 208 | public: 209 | inline virtual const char* what() const throw() override { 210 | return "ALSAException"; 211 | } 212 | }; 213 | 214 | template 215 | inline T clamp(T v, T lo, T hi) { 216 | assert(lo <= hi); 217 | if(v < lo) 218 | return lo; 219 | if(v > hi) 220 | return hi; 221 | return v; 222 | } 223 | template<> inline float clamp(float v, float lo, float hi) { 224 | assert(lo <= hi); 225 | return fmin(fmax(v, lo), hi); 226 | } 227 | template<> inline double clamp(double v, double lo, double hi) { 228 | assert(lo <= hi); 229 | return fmin(fmax(v, lo), hi); 230 | } 231 | 232 | // Generic number-to-string conversion and vice versa 233 | // Unlike the standard functions, these are locale-independent, and the functions never throw exceptions. 234 | template 235 | inline std::string NumToString(T number) { 236 | std::ostringstream ss; 237 | ss << number; 238 | return ss.str(); 239 | } 240 | template 241 | inline bool StringToNum(const std::string& str, T* number) { 242 | std::istringstream ss(str); 243 | ss >> *number; 244 | return !ss.fail(); 245 | } 246 | 247 | // convert weird types from libav/ffmpeg to doubles 248 | inline double ToDouble(const AVRational& r) { 249 | return (double) r.num / (double) r.den; 250 | } 251 | inline double ToDouble(const AVFrac& f) { 252 | return (double) f.val + (double) f.num / (double) f.den; 253 | } 254 | 255 | // high resolution timer 256 | inline int64_t hrt_time_micro() { 257 | timespec ts; 258 | clock_gettime(CLOCK_MONOTONIC, &ts); 259 | return (uint64_t) ts.tv_sec * (uint64_t) 1000000 + (uint64_t) (ts.tv_nsec / 1000); 260 | } 261 | #endif // GLOBAL_H 262 | -------------------------------------------------------------------------------- /grab_test.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2016-09-06T16:07:48 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui widgets x11extras 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = grab_test 12 | TEMPLATE = app 13 | 14 | 15 | SOURCES += main.cpp\ 16 | maintest.cpp \ 17 | xgrab.cpp \ 18 | Logger.cpp \ 19 | AV/Output/BaseEncoder.cpp \ 20 | AV/Output/Muxer.cpp \ 21 | AV/Output/VideoEncoder.cpp 22 | 23 | HEADERS += maintest.h \ 24 | xgrab.h \ 25 | global.h \ 26 | Logger.h \ 27 | TempBuffer.h \ 28 | AV/Output/BaseEncoder.h \ 29 | AV/Output/Muxer.h \ 30 | AV/Output/VideoEncoder.h \ 31 | AV/Output/OutputSettings.h 32 | 33 | FORMS += maintest.ui 34 | 35 | DEFINES += SSR_USE_X86_ASM=1 SSR_USE_FFMPEG_VERSIONS=1 36 | LIBS += -lX11 -lXext -lXfixes -lasound -lavformat -lavcodec -lavutil -lswscale 37 | LIBS += -L/home/domon/ffmpeg_build/lib 38 | INCLUDEPATH += /home/domon/ffmpeg_build/include AV AV/Input AV/Output 39 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "maintest.h" 2 | #include 3 | #include "Logger.h" 4 | int main(int argc, char *argv[]) 5 | { 6 | Logger logger; 7 | av_register_all(); 8 | avcodec_register_all(); 9 | avformat_network_init(); 10 | Q_UNUSED(logger); 11 | QApplication a(argc, argv); 12 | MainTest w; 13 | w.show(); 14 | 15 | return a.exec(); 16 | } 17 | -------------------------------------------------------------------------------- /maintest.cpp: -------------------------------------------------------------------------------- 1 | #include "maintest.h" 2 | #include "ui_maintest.h" 3 | #include "global.h" 4 | #include "xgrab.h" 5 | #include 6 | 7 | MainTest::MainTest(QWidget *parent) : 8 | QMainWindow(parent), 9 | ui(new Ui::MainTest) 10 | { 11 | ui->setupUi(this); 12 | m_should_stop = true; 13 | m_video_frame_rate = 25; 14 | m_output_settings.container_avname="matroska"; 15 | m_output_settings.video_codec_avname="libx264"; 16 | m_output_settings.file="/tmp/test.mkv"; 17 | m_output_settings.video_frame_rate=25; 18 | m_output_settings.video_kbit_rate=5000; 19 | } 20 | 21 | MainTest::~MainTest() 22 | { 23 | delete ui; 24 | if(m_thread.joinable()){ 25 | m_should_stop=true; 26 | m_thread.join(); 27 | } 28 | } 29 | 30 | // Tries to find the real window that corresponds to a top-level window (the actual window without window manager decorations). 31 | // Returns None if it can't find the window (probably because the window is not handled by the window manager). 32 | // Based on the xprop source code (http://cgit.freedesktop.org/xorg/app/xprop/tree/clientwin.c). 33 | static Window X11FindRealWindow(Display* display, Window window) { 34 | 35 | // is this the real window? 36 | Atom actual_type; 37 | int actual_format; 38 | unsigned long items, bytes_left; 39 | unsigned char *data = NULL; 40 | XGetWindowProperty(display, window, XInternAtom(display, "WM_STATE", true), 41 | 0, 0, false, AnyPropertyType, &actual_type, &actual_format, &items, &bytes_left, &data); 42 | if(data != NULL) 43 | XFree(data); 44 | if(actual_type != None) 45 | return window; 46 | 47 | // get the child windows 48 | Window root, parent, *childs; 49 | unsigned int childcount; 50 | if(!XQueryTree(display, window, &root, &parent, &childs, &childcount)) { 51 | return None; 52 | } 53 | 54 | // recursively call this function for all childs 55 | Window real_window = None; 56 | for(unsigned int i = childcount; i > 0; ) { 57 | --i; 58 | Window w = X11FindRealWindow(display, childs[i]); 59 | if(w != None) { 60 | real_window = w; 61 | break; 62 | } 63 | } 64 | 65 | // free child window list 66 | if(childs != NULL) 67 | XFree(childs); 68 | 69 | return real_window; 70 | 71 | } 72 | 73 | // This does some sanity checking on the rubber band rectangle before creating it. 74 | // Rubber bands with width or height zero or extremely large appear to cause problems. 75 | static QRect ValidateRubberBandRectangle(QRect rect) { 76 | QRect screenrect = QApplication::desktop()->screenGeometry(0); 77 | for(int i = 1; i < QApplication::desktop()->screenCount(); ++i) { 78 | screenrect |= QApplication::desktop()->screenGeometry(i); 79 | } 80 | rect = rect.normalized(); 81 | rect &= screenrect.adjusted(-10, -10, 10, 10); 82 | return (rect.isNull())? QRect(-10, -10, 1, 1) : rect; 83 | } 84 | 85 | static AVFrame* CreateVideoFrame(unsigned int width, unsigned int height, AVPixelFormat pixel_format) { 86 | 87 | // get required planes 88 | unsigned int planes = 0; 89 | size_t linesize[3] = {0}, planesize[3] = {0}; 90 | switch(pixel_format) { 91 | case AV_PIX_FMT_YUV444P: { 92 | // Y/U/V = 1 byte per pixel 93 | planes = 3; 94 | linesize[0] = grow_align16(width); planesize[0] = linesize[0] * height; 95 | linesize[1] = grow_align16(width); planesize[1] = linesize[1] * height; 96 | linesize[2] = grow_align16(width); planesize[2] = linesize[2] * height; 97 | break; 98 | } 99 | case AV_PIX_FMT_YUV422P: { 100 | // Y = 1 byte per pixel, U/V = 1 byte per 2x1 pixels 101 | assert(width % 2 == 0); 102 | planes = 3; 103 | linesize[0] = grow_align16(width ); planesize[0] = linesize[0] * height; 104 | linesize[1] = grow_align16(width / 2); planesize[1] = linesize[1] * height; 105 | linesize[2] = grow_align16(width / 2); planesize[2] = linesize[2] * height; 106 | break; 107 | } 108 | case AV_PIX_FMT_YUV420P: { 109 | // Y = 1 byte per pixel, U/V = 1 byte per 2x2 pixels 110 | assert(width % 2 == 0); 111 | assert(height % 2 == 0); 112 | planes = 3; 113 | linesize[0] = grow_align16(width ); planesize[0] = linesize[0] * height ; 114 | linesize[1] = grow_align16(width / 2); planesize[1] = linesize[1] * height / 2; 115 | linesize[2] = grow_align16(width / 2); planesize[2] = linesize[2] * height / 2; 116 | break; 117 | } 118 | case AV_PIX_FMT_NV12: { 119 | assert(width % 2 == 0); 120 | assert(height % 2 == 0); 121 | // planar YUV 4:2:0, 12bpp, 1 plane for Y and 1 plane for the UV components, which are interleaved 122 | // Y = 1 byte per pixel, U/V = 1 byte per 2x2 pixels 123 | planes = 2; 124 | linesize[0] = grow_align16(width); planesize[0] = linesize[0] * height ; 125 | linesize[1] = grow_align16(width); planesize[1] = linesize[1] * height / 2; 126 | break; 127 | } 128 | case AV_PIX_FMT_BGRA: { 129 | // BGRA = 4 bytes per pixel 130 | planes = 1; 131 | linesize[0] = grow_align16(width * 4); planesize[0] = linesize[0] * height; 132 | break; 133 | } 134 | case AV_PIX_FMT_BGR24: { 135 | // BGR = 3 bytes per pixel 136 | planes = 1; 137 | linesize[0] = grow_align16(width * 3); planesize[0] = linesize[0] * height; 138 | break; 139 | } 140 | default: assert(false); break; 141 | } 142 | 143 | // create the frame 144 | size_t totalsize = 0; 145 | for(unsigned int p = 0; p < planes; ++p) { 146 | totalsize += planesize[p]; 147 | } 148 | //alloc frame 149 | AVFrame *frame; 150 | #if SSR_USE_AV_FRAME_ALLOC 151 | frame = av_frame_alloc(); 152 | #else 153 | frame = avcodec_alloc_frame(); 154 | #endif 155 | if(frame == NULL) 156 | std::bad_alloc(); 157 | #if SSR_USE_AVFRAME_EXTENDED_DATA 158 | // ffmpeg docs say that extended_data should point to data if it isn't used 159 | frame->extended_data = frame->data; 160 | #endif 161 | uint8_t *data = (uint8_t*) av_malloc(totalsize); 162 | if(data == NULL) 163 | throw std::bad_alloc(); 164 | 165 | for(unsigned int p = 0; p < planes; ++p) { 166 | frame->data[p] = data; 167 | frame->linesize[p] = linesize[p]; 168 | data += planesize[p]; 169 | } 170 | #if SSR_USE_AVFRAME_WIDTH_HEIGHT 171 | frame->width = width; 172 | frame->height = height; 173 | #endif 174 | #if SSR_USE_AVFRAME_FORMAT 175 | frame->format = pixel_format; 176 | #endif 177 | #if SSR_USE_AVFRAME_SAR 178 | frame->sample_aspect_ratio.num = 1; 179 | frame->sample_aspect_ratio.den = 1; 180 | #endif 181 | 182 | return frame; 183 | 184 | } 185 | 186 | static void Free_avframe(AVFrame *frame){ 187 | if(frame != NULL) { 188 | #if SSR_USE_AV_FRAME_FREE 189 | av_frame_free(&frame); 190 | #elif SSR_USE_AVCODEC_FREE_FRAME 191 | avcodec_free_frame(&frame); 192 | #else 193 | av_free(frame); 194 | #endif 195 | } 196 | } 197 | 198 | void MainTest::SetVideoAreaFromRubberBand() { 199 | m_ret = m_rubber_band_rect.normalized(); 200 | } 201 | 202 | void MainTest::StopGrabbing() { 203 | m_rubber_band.reset(); 204 | setMouseTracking(false); 205 | releaseKeyboard(); 206 | releaseMouse(); 207 | this->raise(); 208 | this->activateWindow(); 209 | if(m_selecting_window) 210 | ui->m_grab_window->setDown(false); 211 | else 212 | ui->m_grab_rect->setDown(false); 213 | m_grabbing = false; 214 | } 215 | 216 | void MainTest::mousePressEvent(QMouseEvent* event) { 217 | if(m_grabbing) { 218 | if(event->button() == Qt::LeftButton) { 219 | if(m_selecting_window) { 220 | // As expected, Qt does not provide any functions to find the window at a specific position, so I have to use Xlib directly. 221 | // I'm not completely sure whether this is the best way to do this, but it appears to work. XQueryPointer returns the window 222 | // currently below the mouse along with the mouse position, but apparently this may not work correctly when the mouse pointer 223 | // is also grabbed (even though it works fine in my test), so I use XTranslateCoordinates instead. Originally I wanted to 224 | // show the rubber band when the mouse hovers over a window (instead of having to click it), but this doesn't work correctly 225 | // since X will simply return a handle the rubber band itself (even though it should be transparent to mouse events). 226 | Window selected_window; 227 | int x, y; 228 | if(XTranslateCoordinates(QX11Info::display(), QX11Info::appRootWindow(), QX11Info::appRootWindow(), event->globalX(), event->globalY(), &x, &y, &selected_window)) { 229 | XWindowAttributes attributes; 230 | if(selected_window != None && XGetWindowAttributes(QX11Info::display(), selected_window, &attributes)) { 231 | 232 | // naive outer/inner rectangle, this won't work for window decorations 233 | m_select_window_outer_rect = QRect(attributes.x, attributes.y, attributes.width + 2 * attributes.border_width, attributes.height + 2 * attributes.border_width); 234 | m_select_window_inner_rect = QRect(attributes.x + attributes.border_width, attributes.y + attributes.border_width, attributes.width, attributes.height); 235 | 236 | // try to find the real window (rather than the decorations added by the window manager) 237 | Window real_window = X11FindRealWindow(QX11Info::display(), selected_window); 238 | if(real_window != None) { 239 | Atom actual_type; 240 | int actual_format; 241 | unsigned long items, bytes_left; 242 | long *data = NULL; 243 | int result = XGetWindowProperty(QX11Info::display(), real_window, XInternAtom(QX11Info::display(), "_NET_FRAME_EXTENTS", true), 244 | 0, 4, false, AnyPropertyType, &actual_type, &actual_format, &items, &bytes_left, (unsigned char**) &data); 245 | if(result == Success) { 246 | if(items == 4 && bytes_left == 0 && actual_format == 32) { // format 32 means 'long', even if long is 64-bit ... 247 | Window child; 248 | // the attributes of the real window only store the *relative* position which is not what we need, so use XTranslateCoordinates again 249 | if(XTranslateCoordinates(QX11Info::display(), real_window, QX11Info::appRootWindow(), 0, 0, &x, &y, &child) 250 | && XGetWindowAttributes(QX11Info::display(), real_window, &attributes)) { 251 | 252 | // finally! 253 | m_select_window_inner_rect = QRect(x, y, attributes.width, attributes.height); 254 | m_select_window_outer_rect = m_select_window_inner_rect.adjusted(-data[0], -data[2], data[1], data[3]); 255 | 256 | } else { 257 | 258 | // I doubt this will ever be needed, but do it anyway 259 | m_select_window_inner_rect = m_select_window_outer_rect.adjusted(data[0], data[2], -data[1], -data[3]); 260 | 261 | } 262 | } 263 | } 264 | if(data != NULL) 265 | XFree(data); 266 | } 267 | 268 | // pick the inner rectangle if the users clicks inside the window, or the outer rectangle otherwise 269 | m_rubber_band_rect = (m_select_window_inner_rect.contains(event->globalPos()))? m_select_window_inner_rect : m_select_window_outer_rect; 270 | m_rubber_band.reset(new QRubberBand(QRubberBand::Line)); 271 | m_rubber_band->setWindowOpacity(0.4); 272 | m_rubber_band->setGeometry(ValidateRubberBandRectangle(m_rubber_band_rect)); 273 | m_rubber_band->show(); 274 | 275 | } 276 | } 277 | } else { 278 | m_rubber_band_rect = QRect(event->globalPos(), QSize(0, 0)); 279 | m_rubber_band.reset(new QRubberBand(QRubberBand::Line)); 280 | m_rubber_band->setWindowOpacity(0.4); 281 | m_rubber_band->setGeometry(ValidateRubberBandRectangle(m_rubber_band_rect)); 282 | m_rubber_band->show(); 283 | } 284 | } else { 285 | StopGrabbing(); 286 | } 287 | event->accept(); 288 | return; 289 | } 290 | event->ignore(); 291 | } 292 | 293 | void MainTest::mouseReleaseEvent(QMouseEvent* event) { 294 | if(m_grabbing) { 295 | if(event->button() == Qt::LeftButton) { 296 | if(m_rubber_band != NULL) { 297 | SetVideoAreaFromRubberBand(); 298 | m_rubber_band->hide(); 299 | } 300 | } 301 | StopGrabbing(); 302 | event->accept(); 303 | qDebug("the rect info is height: %d, width: %d \n", m_ret.height(),m_ret.width()); 304 | //sleep to wait rubber disappear 305 | usleep(50000); 306 | x11.reset(new xgrab(m_ret.x(),m_ret.y(),m_ret.width()/2*2,m_ret.height()/2*2,true,false)); 307 | QSize out_size=QSize(m_ret.width()/2*2,m_ret.height()/2*2); 308 | //badly preview for test 309 | uint8_t *previewer = x11->Forpreview(); 310 | if(previewer != NULL) { 311 | 312 | // create image (data is not copied) 313 | QImage img((uchar *)previewer, out_size.width(), out_size.height(), x11->m_image_stride, QImage::Format_RGB32); 314 | img.scaled(ui->label_2->size()); 315 | ui->label_2->setPixmap(QPixmap::fromImage(img)); 316 | 317 | } 318 | m_should_stop=false; 319 | return; 320 | } 321 | event->ignore(); 322 | } 323 | 324 | void MainTest::mouseMoveEvent(QMouseEvent* event) { 325 | if(m_grabbing) { 326 | if(m_rubber_band != NULL) { 327 | if(m_selecting_window) { 328 | // pick the inner rectangle if the users clicks inside the window, or the outer rectangle otherwise 329 | m_rubber_band_rect = (m_select_window_inner_rect.contains(event->globalPos()))? m_select_window_inner_rect : m_select_window_outer_rect; 330 | } else { 331 | m_rubber_band_rect.setBottomRight(event->globalPos()); 332 | } 333 | m_rubber_band->setGeometry(ValidateRubberBandRectangle(m_rubber_band_rect)); 334 | } 335 | event->accept(); 336 | return; 337 | } 338 | event->ignore(); 339 | } 340 | 341 | void MainTest::paintEvent(QPaintEvent* event) { 342 | Q_UNUSED(event); 343 | } 344 | 345 | void MainTest::StartGrabbing(){ 346 | m_grabbing = true; 347 | if(m_selecting_window) 348 | ui->m_grab_window->setDown(true); 349 | else 350 | ui->m_grab_rect->setDown(true); 351 | this->lower(); 352 | grabMouse(Qt::CrossCursor); 353 | grabKeyboard(); 354 | setMouseTracking(true); 355 | } 356 | 357 | void MainTest::keyPressEvent(QKeyEvent* event) { 358 | if(m_grabbing) { 359 | if(event->key() == Qt::Key_Escape) { 360 | StopGrabbing(); 361 | return; 362 | } 363 | event->accept(); 364 | return; 365 | } 366 | event->ignore(); 367 | } 368 | 369 | void MainTest::on_m_grab_rect_clicked() 370 | { 371 | m_selecting_window = false; 372 | StartGrabbing(); 373 | } 374 | 375 | void MainTest::on_m_grab_window_clicked() 376 | { 377 | m_selecting_window = true; 378 | StartGrabbing(); 379 | } 380 | 381 | void MainTest::GrabThread() { 382 | 383 | int64_t last_timestamp = hrt_time_micro(); 384 | int64_t timestamp; 385 | int64_t next_timestamp = last_timestamp; 386 | int64_t local_pts=0; 387 | //while(m_should_stop)sleep(1); 388 | std::unique_ptr muxer(new Muxer(m_output_settings.container_avname,m_output_settings.file)); 389 | //VideoEncoder *video_encoder = NULL; 390 | AVFrame *frame=NULL; 391 | //int out_stride = grow_align16(m_ret.width() * 4); 392 | QSize out_size=QSize(m_ret.width()/2*2,m_ret.height()/2*2); 393 | 394 | if(!m_output_settings.video_codec_avname.isEmpty()) 395 | video_encoder = muxer->AddVideoEncoder(m_output_settings.video_codec_avname, m_output_settings.video_options, m_output_settings.video_kbit_rate * 1024, 396 | out_size.width(), out_size.height(), m_output_settings.video_frame_rate); 397 | muxer->Start(); 398 | while(!m_should_stop){ 399 | next_timestamp = last_timestamp + (int64_t)(1000000/m_video_frame_rate); 400 | timestamp = hrt_time_micro(); 401 | if(timestamp < next_timestamp){ 402 | usleep(next_timestamp-timestamp); 403 | continue; 404 | } 405 | //usleep(40000); 406 | frame=CreateVideoFrame(out_size.width(),out_size.height(), video_encoder->GetPixelFormat()); 407 | x11->GetQimage(frame, video_encoder->GetPixelFormat(), out_size); 408 | frame->pts=local_pts;//local_pts; 409 | local_pts++; 410 | 411 | //badly preview for test 412 | uint8_t *previewer = x11->Forpreview(); 413 | if(previewer != NULL) { 414 | 415 | // create image (data is not copied) 416 | QImage img((uchar *)previewer, out_size.width(), out_size.height(), x11->m_image_stride, QImage::Format_RGB32); 417 | ui->label_2->setPixmap(QPixmap::fromImage(img)); 418 | 419 | } 420 | 421 | video_encoder->EncodeFrame(frame); 422 | 423 | last_timestamp = timestamp; 424 | } 425 | 426 | } 427 | 428 | void MainTest::on_start_clicked() 429 | { 430 | if(m_thread.joinable()){ 431 | m_should_stop=true; 432 | m_thread.join(); 433 | } 434 | m_should_stop=false; 435 | m_thread=std::thread(&MainTest::GrabThread, this); 436 | } 437 | 438 | void MainTest::on_stop_clicked() 439 | { 440 | if(m_thread.joinable()){ 441 | m_should_stop=true; 442 | m_thread.join(); 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /maintest.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINTEST_H 2 | #define MAINTEST_H 3 | 4 | #include "global.h" 5 | #include "Muxer.h" 6 | #include "VideoEncoder.h" 7 | #include "OutputSettings.h" 8 | 9 | namespace Ui { 10 | class MainTest; 11 | } 12 | 13 | class xgrab; 14 | 15 | /////////////////////////////////////////////// 16 | class MainTest : public QMainWindow 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | explicit MainTest(QWidget *parent = 0); 22 | ~MainTest(); 23 | enum enum_video_area { 24 | VIDEO_AREA_SCREEN, 25 | VIDEO_AREA_FIXED, 26 | VIDEO_AREA_CURSOR, 27 | VIDEO_AREA_GLINJECT, 28 | VIDEO_AREA_COUNT // must be last 29 | }; 30 | 31 | protected: 32 | virtual void mousePressEvent(QMouseEvent* event) override; 33 | virtual void mouseReleaseEvent(QMouseEvent* event) override; 34 | virtual void mouseMoveEvent(QMouseEvent* event) override; 35 | virtual void keyPressEvent(QKeyEvent* event) override; 36 | virtual void paintEvent(QPaintEvent* event) override; 37 | 38 | signals: 39 | void NeedsUpdate(); 40 | 41 | private: 42 | void StartGrabbing(); 43 | void StopGrabbing(); 44 | void SetVideoAreaFromRubberBand(); 45 | void GrabThread(); 46 | void PushVideoFrame(); 47 | bool m_grabbing, m_selecting_window, m_should_stop; 48 | std::thread m_thread; 49 | std::unique_ptr m_rubber_band, m_recording_frame; 50 | QRect m_ret,m_rubber_band_rect, m_select_window_outer_rect, m_select_window_inner_rect; 51 | uint32_t m_video_frame_rate; 52 | 53 | //muxer & encoders 54 | OutputSettings m_output_settings; 55 | std::unique_ptr m_muxer; 56 | VideoEncoder *m_video_encoder; 57 | 58 | private slots: 59 | 60 | void on_m_grab_rect_clicked(); 61 | void on_m_grab_window_clicked(); 62 | void on_start_clicked(); 63 | 64 | void on_stop_clicked(); 65 | 66 | private: 67 | Ui::MainTest *ui; 68 | VideoEncoder *video_encoder; 69 | std::unique_ptr x11; 70 | 71 | private: 72 | QWidget *centralWidget; 73 | QButtonGroup *m_buttongroup_video_area; 74 | QPushButton *m_pushbutton_video_select_rectangle, *m_pushbutton_video_select_window, *m_pushbutton_video_opengl_settings; 75 | QLabel *m_label_video_x, *m_label_video_y, *m_label_video_w, *m_label_video_h; 76 | QSpinBox *m_spinbox_video_frame_rate; 77 | QCheckBox *m_checkbox_scale; 78 | QLabel *m_label_video_scaled_w, *m_label_video_scaled_h; 79 | QSpinBox *m_spinbox_video_scaled_w, *m_spinbox_video_scaled_h; 80 | QCheckBox *m_checkbox_record_cursor; 81 | 82 | QCheckBox *m_checkbox_audio_enable; 83 | QLabel *m_label_audio_backend; 84 | QComboBox *m_combobox_audio_backend; 85 | QLabel *m_label_alsa_source; 86 | QComboBox *m_combobox_alsa_source; 87 | QPushButton *m_pushbutton_alsa_refresh; 88 | }; 89 | 90 | #endif // MAINTEST_H 91 | -------------------------------------------------------------------------------- /maintest.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainTest 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | MainTest 15 | 16 | 17 | 18 | 19 | 20 | 0 21 | 0 22 | 71 23 | 16 24 | 25 | 26 | 27 | grab_0_0_1 28 | 29 | 30 | 31 | 32 | 33 | 10 34 | 20 35 | 361 36 | 171 37 | 38 | 39 | 40 | Img is here 41 | 42 | 43 | 44 | 45 | 46 | 310 47 | 180 48 | 80 49 | 26 50 | 51 | 52 | 53 | Grab_Rect 54 | 55 | 56 | 57 | 58 | 59 | 210 60 | 180 61 | 91 62 | 26 63 | 64 | 65 | 66 | Grab_window 67 | 68 | 69 | 70 | 71 | 72 | 210 73 | 210 74 | 80 75 | 26 76 | 77 | 78 | 79 | start 80 | 81 | 82 | 83 | 84 | 85 | 310 86 | 210 87 | 80 88 | 26 89 | 90 | 91 | 92 | stop 93 | 94 | 95 | 96 | 97 | 98 | 99 | 0 100 | 0 101 | 400 102 | 23 103 | 104 | 105 | 106 | 107 | demo 108 | 109 | 110 | 111 | 112 | 113 | 114 | TopToolBarArea 115 | 116 | 117 | false 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /test.mkv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domonforyou/Screen_Record_Demo/996c0a023e09d8eb222da753cb7e066911e4c519/test.mkv -------------------------------------------------------------------------------- /xgrab.cpp: -------------------------------------------------------------------------------- 1 | #include "xgrab.h" 2 | #include "Logger.h" 3 | #include 4 | #include 5 | #include 6 | 7 | // Converts a X11 image format to a format that libav/ffmpeg understands. 8 | static AVPixelFormat X11ImageGetPixelFormat(XImage* image) { 9 | switch(image->bits_per_pixel) { 10 | case 8: return AV_PIX_FMT_PAL8; 11 | case 16: { 12 | if(image->red_mask == 0xf800 && image->green_mask == 0x07e0 && image->blue_mask == 0x001f) return AV_PIX_FMT_RGB565; 13 | if(image->red_mask == 0x7c00 && image->green_mask == 0x03e0 && image->blue_mask == 0x001f) return AV_PIX_FMT_RGB555; 14 | break; 15 | } 16 | case 24: { 17 | if(image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) return AV_PIX_FMT_BGR24; 18 | if(image->red_mask == 0x0000ff && image->green_mask == 0x00ff00 && image->blue_mask == 0xff0000) return AV_PIX_FMT_RGB24; 19 | break; 20 | } 21 | case 32: { 22 | if(image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) return AV_PIX_FMT_BGRA; 23 | if(image->red_mask == 0x0000ff && image->green_mask == 0x00ff00 && image->blue_mask == 0xff0000) return AV_PIX_FMT_RGBA; 24 | if(image->red_mask == 0xff000000 && image->green_mask == 0x00ff0000 && image->blue_mask == 0x0000ff00) return AV_PIX_FMT_ABGR; 25 | if(image->red_mask == 0x0000ff00 && image->green_mask == 0x00ff0000 && image->blue_mask == 0xff000000) return AV_PIX_FMT_ARGB; 26 | break; 27 | } 28 | } 29 | fprintf(stderr,"error: x11 get pixelformat \n"); 30 | } 31 | 32 | // clears a rectangular area of an image (i.e. sets the memory to zero, which will most likely make the image black) 33 | static void X11ImageClearRectangle(XImage* image, unsigned int x, unsigned int y, unsigned int w, unsigned int h) { 34 | 35 | // check the image format 36 | if(image->bits_per_pixel % 8 != 0) 37 | return; 38 | unsigned int pixel_bytes = image->bits_per_pixel / 8; 39 | 40 | // fill the rectangle with zeros 41 | for(unsigned int j = 0; j < h; ++j) { 42 | uint8_t *image_row = (uint8_t*) image->data + image->bytes_per_line * (y + j); 43 | memset(image_row + pixel_bytes * x, 0, pixel_bytes * w); 44 | } 45 | 46 | } 47 | 48 | // Draws the current cursor at the current position on the image. Requires XFixes. 49 | // Note: In the original code from x11grab, the variables for red and blue are swapped 50 | // (which doesn't change the result, but it's confusing). 51 | // Note 2: This function assumes little-endianness. 52 | // Note 3: This function only supports 24-bit and 32-bit images (it does nothing for other bit depths). 53 | static void X11ImageDrawCursor(Display* dpy, XImage* image, int recording_area_x, int recording_area_y) { 54 | 55 | // check the image format 56 | unsigned int pixel_bytes, r_offset, g_offset, b_offset; 57 | if(image->bits_per_pixel == 24 && image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) { 58 | pixel_bytes = 3; 59 | r_offset = 2; g_offset = 1; b_offset = 0; 60 | } else if(image->bits_per_pixel == 24 && image->red_mask == 0x0000ff && image->green_mask == 0x00ff00 && image->blue_mask == 0xff0000) { 61 | pixel_bytes = 3; 62 | r_offset = 0; g_offset = 1; b_offset = 2; 63 | } else if(image->bits_per_pixel == 32 && image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) { 64 | pixel_bytes = 4; 65 | r_offset = 2; g_offset = 1; b_offset = 0; 66 | } else if(image->bits_per_pixel == 32 && image->red_mask == 0x0000ff && image->green_mask == 0x00ff00 && image->blue_mask == 0xff0000) { 67 | pixel_bytes = 4; 68 | r_offset = 0; g_offset = 1; b_offset = 2; 69 | } else if(image->bits_per_pixel == 32 && image->red_mask == 0xff000000 && image->green_mask == 0x00ff0000 && image->blue_mask == 0x0000ff00) { 70 | pixel_bytes = 4; 71 | r_offset = 3; g_offset = 2; b_offset = 1; 72 | } else if(image->bits_per_pixel == 32 && image->red_mask == 0x0000ff00 && image->green_mask == 0x00ff0000 && image->blue_mask == 0xff000000) { 73 | pixel_bytes = 4; 74 | r_offset = 1; g_offset = 2; b_offset = 3; 75 | } else { 76 | return; 77 | } 78 | 79 | // get the cursor 80 | XFixesCursorImage *xcim = XFixesGetCursorImage(dpy); 81 | if(xcim == NULL) 82 | return; 83 | 84 | // calculate the position of the cursor 85 | int x = xcim->x - xcim->xhot - recording_area_x; 86 | int y = xcim->y - xcim->yhot - recording_area_y; 87 | 88 | // calculate the part of the cursor that's visible 89 | int cursor_left = std::max(0, -x), cursor_right = std::min((int) xcim->width, image->width - x); 90 | int cursor_top = std::max(0, -y), cursor_bottom = std::min((int) xcim->height, image->height - y); 91 | 92 | // draw the cursor 93 | // XFixesCursorImage uses 'long' instead of 'int' to store the cursor images, which is a bit weird since 94 | // 'long' is 64-bit on 64-bit systems and only 32 bits are actually used. The image uses premultiplied alpha. 95 | for(int j = cursor_top; j < cursor_bottom; ++j) { 96 | unsigned long *cursor_row = xcim->pixels + xcim->width * j; 97 | uint8_t *image_row = (uint8_t*) image->data + image->bytes_per_line * (y + j); 98 | for(int i = cursor_left; i < cursor_right; ++i) { 99 | unsigned long cursor_pixel = cursor_row[i]; 100 | uint8_t *image_pixel = image_row + pixel_bytes * (x + i); 101 | int cursor_a = (uint8_t) (cursor_pixel >> 24); 102 | int cursor_r = (uint8_t) (cursor_pixel >> 16); 103 | int cursor_g = (uint8_t) (cursor_pixel >> 8); 104 | int cursor_b = (uint8_t) (cursor_pixel >> 0); 105 | if(cursor_a == 255) { 106 | image_pixel[r_offset] = cursor_r; 107 | image_pixel[g_offset] = cursor_g; 108 | image_pixel[b_offset] = cursor_b; 109 | } else { 110 | image_pixel[r_offset] = (image_pixel[r_offset] * (255 - cursor_a) + 127) / 255 + cursor_r; 111 | image_pixel[g_offset] = (image_pixel[g_offset] * (255 - cursor_a) + 127) / 255 + cursor_g; 112 | image_pixel[b_offset] = (image_pixel[b_offset] * (255 - cursor_a) + 127) / 255 + cursor_b; 113 | } 114 | } 115 | } 116 | 117 | // free the cursor 118 | XFree(xcim); 119 | 120 | } 121 | 122 | QSize CalculateScaledSize(QSize in, QSize out) { 123 | assert(in.width() > 0 && in.height() > 0); 124 | if(in.width() <= out.width() && in.height() <= out.height()) 125 | return in; 126 | if(in.width() * out.height() > out.width() * in.height()) 127 | return QSize(out.width(), (out.width() * in.height() + in.width() / 2) / in.width()); 128 | else 129 | return QSize((out.height() * in.width() + in.height() / 2) / in.height(), out.height()); 130 | } 131 | 132 | xgrab::xgrab(unsigned int x, unsigned int y, unsigned int width, unsigned int height, bool record_cursor, bool follow_cursor) { 133 | 134 | m_x = x; 135 | m_y = y; 136 | m_width = width; 137 | m_height = height; 138 | m_record_cursor = record_cursor; 139 | m_follow_cursor = follow_cursor; 140 | 141 | m_x11_display = NULL; 142 | m_x11_shm_info.shmid = -1; 143 | m_x11_shm_info.shmaddr = (char*) -1; 144 | m_x11_shm_server_attached = false; 145 | m_x11_image = NULL; 146 | m_sws_context = NULL; 147 | 148 | if(m_width == 0 || m_height == 0) { 149 | Logger::LogError("[X11Input::Init] " + Logger::tr("Error: Width or height is zero!")); 150 | throw X11Exception(); 151 | } 152 | if(m_width > 10000 || m_height > 10000) { 153 | Logger::LogError("[X11Input::Init] " + Logger::tr("Error: Width or height is too large, the maximum width and height is %1!").arg(10000)); 154 | throw X11Exception(); 155 | } 156 | 157 | try { 158 | Init(); 159 | } catch(...) { 160 | Free(); 161 | throw; 162 | } 163 | 164 | } 165 | 166 | xgrab::~xgrab(){ 167 | Free(); 168 | } 169 | 170 | void xgrab::Init() { 171 | 172 | // do the X11 stuff 173 | // we need a separate display because the existing one would interfere with what Qt is doing in some cases 174 | m_x11_display = XOpenDisplay(NULL); //QX11Info::display(); 175 | if(m_x11_display == NULL) { 176 | fprintf(stderr, "error \n"); 177 | } 178 | m_x11_screen = DefaultScreen(m_x11_display); //QX11Info::appScreen(); 179 | m_x11_root = RootWindow(m_x11_display, m_x11_screen); //QX11Info::appRootWindow(m_x11_screen); 180 | m_x11_visual = DefaultVisual(m_x11_display, m_x11_screen); //(Visual*) QX11Info::appVisual(m_x11_screen); 181 | m_x11_depth = DefaultDepth(m_x11_display, m_x11_screen); //QX11Info::appDepth(m_x11_screen); 182 | m_x11_use_shm = XShmQueryExtension(m_x11_display); 183 | if(m_x11_use_shm) { 184 | fprintf(stderr, "use_shm \n"); 185 | m_x11_image = XShmCreateImage(m_x11_display, m_x11_visual, m_x11_depth, ZPixmap, NULL, &m_x11_shm_info, m_width, m_height); 186 | if(m_x11_image == NULL) { 187 | fprintf(stderr, "error \n"); 188 | } 189 | m_x11_shm_info.shmid = shmget(IPC_PRIVATE, m_x11_image->bytes_per_line * m_x11_image->height, IPC_CREAT | 0700); 190 | if(m_x11_shm_info.shmid == -1) { 191 | fprintf(stderr, "error \n"); 192 | } 193 | m_x11_shm_info.shmaddr = m_x11_image->data = (char*) shmat(m_x11_shm_info.shmid, NULL, SHM_RND); 194 | if(m_x11_shm_info.shmaddr == (char*) -1) { 195 | fprintf(stderr, "error \n"); 196 | } 197 | m_x11_shm_info.readOnly = false; 198 | // the server will attach later 199 | } else { 200 | fprintf(stderr, "not_use_shm \n"); 201 | } 202 | 203 | // showing the cursor requires XFixes (which should be supported on any modern X server, but let's check it anyway) 204 | if(m_record_cursor) { 205 | int event, error; 206 | if(!XFixesQueryExtension(m_x11_display, &event, &error)) { 207 | fprintf(stderr, "error \n"); 208 | m_record_cursor = false; 209 | } 210 | } 211 | 212 | // get screen configuration information, so we can replace the unused areas with black rectangles (rather than showing random uninitialized memory) 213 | // this also used by the mouse following code to make sure that the rectangle stays on the screen 214 | //connect(QApplication::desktop(), SIGNAL(screenCountChanged(int)), this, SLOT(UpdateScreenConfiguration())); 215 | //connect(QApplication::desktop(), SIGNAL(resized(int)), this, SLOT(UpdateScreenConfiguration())); 216 | //UpdateScreenConfiguration(); 217 | 218 | // initialize frame counter 219 | //m_frame_counter = 0; 220 | //m_fps_last_timestamp = hrt_time_micro(); 221 | //m_fps_last_counter = 0; 222 | //m_fps_current = 0.0; 223 | 224 | // start input thread 225 | //m_should_stop = false; 226 | //m_error_occurred = false; 227 | //m_thread = std::thread(&X11Input::InputThread, this); 228 | 229 | } 230 | 231 | void xgrab::Free() { 232 | if(m_x11_shm_server_attached) { 233 | XShmDetach(m_x11_display, &m_x11_shm_info); 234 | m_x11_shm_server_attached = false; 235 | } 236 | if(m_x11_shm_info.shmaddr != (char*) -1) { 237 | shmdt(m_x11_shm_info.shmaddr); 238 | m_x11_shm_info.shmaddr = (char*) -1; 239 | } 240 | if(m_x11_shm_info.shmid != -1) { 241 | shmctl(m_x11_shm_info.shmid, IPC_RMID, NULL); 242 | m_x11_shm_info.shmid = -1; 243 | } 244 | if(m_x11_image != NULL) { 245 | XDestroyImage(m_x11_image); 246 | m_x11_image = NULL; 247 | } 248 | if(m_x11_display != NULL) { 249 | XCloseDisplay(m_x11_display); 250 | m_x11_display = NULL; 251 | } 252 | if(m_sws_context != NULL){ 253 | sws_freeContext(m_sws_context); 254 | m_sws_context = NULL; 255 | } 256 | } 257 | 258 | uint8_t * xgrab::Forpreview(){ 259 | int grab_x = m_x; 260 | int grab_y = m_y; 261 | // get the image 262 | //fprintf(stderr,"Info : GetImage! \n"); 263 | if(m_x11_use_shm) { 264 | if(!m_x11_shm_server_attached) { 265 | if(!XShmAttach(m_x11_display, &m_x11_shm_info)) { 266 | fprintf(stderr,"Error: Can't attach server to shared memory! \n"); 267 | } 268 | m_x11_shm_server_attached = true; 269 | } 270 | if(!XShmGetImage(m_x11_display, m_x11_root, m_x11_image, grab_x, grab_y, AllPlanes)) { 271 | fprintf(stderr,"Error: Can't get image (using shared memory)!\n"); 272 | } 273 | } else { 274 | if(m_x11_image != NULL) { 275 | XDestroyImage(m_x11_image); 276 | m_x11_image = NULL; 277 | } 278 | m_x11_image = XGetImage(m_x11_display, m_x11_root, grab_x, grab_y, m_width, m_height, AllPlanes, ZPixmap); 279 | if(m_x11_image == NULL) { 280 | fprintf(stderr,"Error: Can't get image (not using shared memory)!\n"); 281 | } 282 | } 283 | 284 | // draw the cursor 285 | if(m_record_cursor) { 286 | X11ImageDrawCursor(m_x11_display, m_x11_image, grab_x, grab_y); 287 | } 288 | // push the frame 289 | uint8_t *image_data = (uint8_t*) m_x11_image->data; 290 | m_image_stride = m_x11_image->bytes_per_line; 291 | 292 | return image_data; 293 | } 294 | 295 | void xgrab::GetQimage(AVFrame *frame, AVPixelFormat out_pixel_format, QSize out_size){ 296 | int grab_x = m_x; 297 | int grab_y = m_y; 298 | // get the image 299 | //fprintf(stderr,"Info : GetImage! \n"); 300 | if(m_x11_use_shm) { 301 | if(!m_x11_shm_server_attached) { 302 | if(!XShmAttach(m_x11_display, &m_x11_shm_info)) { 303 | fprintf(stderr,"Error: Can't attach server to shared memory! \n"); 304 | } 305 | m_x11_shm_server_attached = true; 306 | } 307 | if(!XShmGetImage(m_x11_display, m_x11_root, m_x11_image, grab_x, grab_y, AllPlanes)) { 308 | fprintf(stderr,"Error: Can't get image (using shared memory)!\n"); 309 | } 310 | } else { 311 | if(m_x11_image != NULL) { 312 | XDestroyImage(m_x11_image); 313 | m_x11_image = NULL; 314 | } 315 | m_x11_image = XGetImage(m_x11_display, m_x11_root, grab_x, grab_y, m_width, m_height, AllPlanes, ZPixmap); 316 | if(m_x11_image == NULL) { 317 | fprintf(stderr,"Error: Can't get image (not using shared memory)!\n"); 318 | } 319 | } 320 | 321 | // draw the cursor 322 | if(m_record_cursor) { 323 | X11ImageDrawCursor(m_x11_display, m_x11_image, grab_x, grab_y); 324 | } 325 | // push the frame 326 | uint8_t *image_data = (uint8_t*) m_x11_image->data; 327 | m_image_stride = m_x11_image->bytes_per_line; 328 | AVPixelFormat x11_image_format = X11ImageGetPixelFormat(m_x11_image); 329 | 330 | ReadVideoFrame(m_width, m_height, image_data, m_image_stride, x11_image_format, frame, out_size, out_pixel_format); 331 | } 332 | 333 | void xgrab::ReadVideoFrame(unsigned int width, unsigned int height, const uint8_t* data, int stride, 334 | AVPixelFormat format, AVFrame *frame, QSize out_size, AVPixelFormat out_format) { 335 | 336 | m_sws_context = sws_getCachedContext(m_sws_context, width, height, format, 337 | out_size.width(), out_size.height(), out_format, 338 | SWS_BILINEAR, NULL, NULL, NULL); 339 | if(m_sws_context == NULL) { 340 | Logger::LogError("[FastScaler::Scale] " + Logger::tr("Error: Can't get swscale context!", "Don't translate 'swscale'")); 341 | throw LibavException(); 342 | } 343 | sws_setColorspaceDetails(m_sws_context, 344 | sws_getCoefficients(SWS_CS_DEFAULT), 0, //TODO// need to change this for actual YUV inputs (e.g. webcam) 345 | sws_getCoefficients(SWS_CS_ITU709), 0, 346 | 0, 1 << 16, 1 << 16); 347 | 348 | int slice_height = sws_scale(m_sws_context, &data, &stride, 0, height, frame->data, frame->linesize); 349 | 350 | } 351 | -------------------------------------------------------------------------------- /xgrab.h: -------------------------------------------------------------------------------- 1 | #ifndef XGRAB_H 2 | #define XGRAB_H 3 | 4 | #include "global.h" 5 | #include "TempBuffer.h" 6 | 7 | class xgrab 8 | { 9 | public: 10 | xgrab(unsigned int x, unsigned int y, unsigned int width, unsigned int height, bool record_cursor, bool follow_cursor); 11 | ~xgrab(); 12 | void GetQimage(AVFrame *frame, AVPixelFormat out_pixel_format, QSize out_size); 13 | uint8_t * Forpreview(); 14 | void ReadVideoFrame(unsigned int width, unsigned int height, const uint8_t* data, int stride, 15 | AVPixelFormat format, AVFrame *frame, QSize out_size, AVPixelFormat out_format); 16 | 17 | public: 18 | std::shared_ptr > m_image_buffer; 19 | int m_image_stride; 20 | QSize m_image_size; 21 | 22 | private: 23 | unsigned int m_x, m_y, m_width, m_height; 24 | bool m_record_cursor, m_follow_cursor; 25 | 26 | Display *m_x11_display; 27 | int m_x11_screen; 28 | Window m_x11_root; 29 | Visual *m_x11_visual; 30 | int m_x11_depth; 31 | bool m_x11_use_shm; 32 | XShmSegmentInfo m_x11_shm_info; 33 | bool m_x11_shm_server_attached; 34 | XImage *m_x11_image; 35 | SwsContext *m_sws_context; 36 | 37 | private: 38 | void Init(); 39 | void Free(); 40 | }; 41 | 42 | QSize CalculateScaledSize(QSize in, QSize out); 43 | 44 | #endif // XGRAB_H 45 | --------------------------------------------------------------------------------