diff options
| author | Comment <tim@gray.(none)> | 2013-04-15 03:15:33 +0100 |
|---|---|---|
| committer | Comment <tim@gray.(none)> | 2013-04-15 03:15:33 +0100 |
| commit | 25b336a2b34d7a9bcf9e52a16ae962a1e261ae13 (patch) | |
| tree | b5426cdcad9dd90d28650bcc97b7ac42f04b2f44 | |
| parent | 4c99697c528e11a4195b572bf9f72f80c2fe3ea6 (diff) | |
ofxMovieExporter ideas
| -rw-r--r-- | rotord/ofxMovieExporter.cpp | 494 | ||||
| -rw-r--r-- | rotord/ofxMovieExporter.h | 209 | ||||
| -rwxr-xr-x | rotord/renderContext.h | 0 | ||||
| -rwxr-xr-x | rotord/rotor.h | 5 |
4 files changed, 707 insertions, 1 deletions
diff --git a/rotord/ofxMovieExporter.cpp b/rotord/ofxMovieExporter.cpp new file mode 100644 index 0000000..f7c47de --- /dev/null +++ b/rotord/ofxMovieExporter.cpp @@ -0,0 +1,494 @@ +/* + * ofxMovieExporter.cpp + * + * Copyright (c) 2011, Neil Mendoza, http://www.neilmendoza.com + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of 16b.it nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +#include "ofxMovieExporter.h" +//#include "ofThread.h" + +namespace itg +{ + const std::string ofxMovieExporter::FILENAME_PREFIX = "capture"; + const std::string ofxMovieExporter::CONTAINER = "mp4"; + + ofxMovieExporter::ofxMovieExporter() { + outputFormat = NULL; + formatCtx = NULL; + videoStream = NULL; + + codec = NULL; + codecCtx = NULL; + convertCtx = NULL; + + inPixels = NULL; + outPixels = NULL; + encodedBuf = NULL; + + inFrame = NULL; + outFrame = NULL; + + posX = 0; + posY = 0; + //inW = ofGetWidth(); + //inH = ofGetHeight(); + //outW = ofGetWidth(); + //outH = ofGetHeight(); + + bool usePixelSource = false; + pixelSource = NULL; + } + + void ofxMovieExporter::setup( + int outW, + int outH, + int bitRate, + int frameRate, + AVCodecID codecId, + std::string container) + { + if (outW % 2 == 1 || outH % 2 == 1) //ofLog(OF_LOG_ERROR, "ofxMovieExporter: Resolution must be a multiple of 2"); + + this->outW = outW; + this->outH = outH; + this->frameRate = frameRate; + this->bitRate = bitRate; + this->codecId = codecId; + this->container = container; + + frameInterval = 1.f / (float)frameRate; + + // HACK HACK HACK + // Time not syncing + // probably related to codec ticks_per_frame + frameInterval /= 3.f; + recording = false; + numCaptures = 0; + + // do one time encoder set up + av_register_all(); + convertCtx = sws_getContext(inW, inH, PIX_FMT_RGB24, outW, outH, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); + + allocateMemory(); + } + + ofxMovieExporter::~ofxMovieExporter() + { + if (recording) finishRecord(); + + //stopThread(true); + clearMemory(); + } + + void ofxMovieExporter::record(std::string filePrefix, std::string folderPath) + { + initEncoder(); + + std::ostringstream oss; + oss << folderPath; + if (folderPath != "" && (folderPath[folderPath.size()-1] != '/' && folderPath[folderPath.size()-1] != '\\')) + oss << "/"; + oss << filePrefix << numCaptures << "." << container; + outFileName = oss.str(); + + // open the output file + //if (url_fopen(&formatCtx->pb, outFileName.c_str(), 'wb' ) < 0) //url_fopen URL_WRONLY + // ofLog(OF_LOG_ERROR, "ofxMovieExporter: Could not open file %s", outFileName.c_str()); + if (avio_open(&formatCtx->pb, outFileName.c_str(), AVIO_FLAG_WRITE) < 0) { + + cerr << "ofxMovieExporter: Could not open file "<< outFileName<<endl; + } + + + //ofAddListener(ofEvents.draw, this, &ofxMovieExporter::checkFrame); + + AVDictionary *options; //= NULL; causes a forward declaration error!? + options=NULL; + // write the stream header, if any + avformat_write_header(formatCtx,&options); + + lastFrameTime = 0; + frameNum = 0; + recording = true; +//#ifdef _THREAD_CAPTURE +// startThread(true, false); +//#endif + } + + void ofxMovieExporter::stop() + { + //ofRemoveListener(ofEvents.draw, this, &ofxMovieExporter::checkFrame); + recording = false; + numCaptures++; +//#ifndef _THREAD_CAPTURE +// finishRecord(); +//#endif + } + + void ofxMovieExporter::setRecordingArea(int x, int y, int w, int h) + { + posX = x; + posY = y; + inW = w; + inH = h; + } + + void ofxMovieExporter::setRecordingArea(int rect) + { + /* + posX = rect.x; + posY = rect.y; + inW = rect.width; + inH = rect.height; + */ + } + + void ofxMovieExporter::resetRecordingArea() + { + posX = 0; + posY = 0; + //inW = ofGetViewportWidth(); + //inH = ofGetViewportHeight(); + } + + int ofxMovieExporter::getRecordingArea() + { + return 0; //ofRectangle(posX, posY, inW, inH); + } + + void ofxMovieExporter::setPixelSource(unsigned char* pixels, int w, int h) + { + if (isRecording()) + stop(); + + if (pixels == NULL) + { + //ofLog(OF_LOG_ERROR, "ofxMovieExporter: Could not set NULL pixel source"); + return; + } + pixelSource = pixels; + inW = w; + inH = h; + usePixelSource = true; + + // resetup encoder etc + setup(outW, outH, bitRate, frameRate, codecId, container); + } + + void ofxMovieExporter::resetPixelSource() + { + usePixelSource = false; + pixelSource = NULL; + //inW = ofGetViewportWidth(); + //inH = ofGetViewportHeight(); + + // resetup encoder etc + setup(outW, outH, bitRate, frameRate, codecId, container); + } + + int ofxMovieExporter::getNumCaptures() + { + return numCaptures; + } + + void ofxMovieExporter::resetNumCaptures() + { + numCaptures = 0; + } + +// PRIVATE + + void ofxMovieExporter::finishRecord() + { + av_write_trailer(formatCtx); + + // free the encoder + avcodec_close(codecCtx); + for(int i = 0; i < formatCtx->nb_streams; i++) + { + av_freep(&formatCtx->streams[i]->codec); + av_freep(&formatCtx->streams[i]); + } + av_free(formatCtx); + formatCtx = NULL; + //url_fclose(formatCtx->pb); + } + +/* +#ifdef _THREAD_CAPTURE + void ofxMovieExporter::threadedFunction() + { + while (isThreadRunning()) + { + if (!frameQueue.empty()) + { + float start = ofGetElapsedTimef(); + frameQueueMutex.lock(); + inPixels = frameQueue.front(); + frameQueue.pop_front(); + frameQueueMutex.unlock(); + + encodeFrame(); + + frameMemMutex.lock(); + frameMem.push_back(inPixels); + frameMemMutex.unlock(); + if (ofGetElapsedTimef() - start < frameInterval) ofSleepMillis(1000.f * (frameInterval - (ofGetElapsedTimef() - start))); + } + else if (!recording) + { + finishRecord(); + stopThread(true); + } + } + } +#endif +*/ + + void ofxMovieExporter::checkFrame() //ofEventArgs& args) + { + if (1) //ofGetElapsedTimef() - lastFrameTime >= frameInterval) + { +/* +#ifdef _THREAD_CAPTURE + unsigned char* pixels; + if (!frameMem.empty()) + { + frameMemMutex.lock(); + pixels = frameMem.back(); + frameMem.pop_back(); + frameMemMutex.unlock(); + } + else + { + pixels = new unsigned char[inW * inH * 3]; + } + + if (!usePixelSource) + { + // this part from ofImage::saveScreen + int screenHeight = ofGetViewportHeight(); // if we are in a FBO or other viewport, this fails: ofGetHeight(); + int screenY = screenHeight - posY; + screenY -= inH; // top, bottom issues + + glReadPixels(posX, screenY, inW, inH, GL_RGB, GL_UNSIGNED_BYTE, pixels); + } + else + { + memcpy(pixels, pixelSource, inW * inH * 3); + } + + frameQueueMutex.lock(); + frameQueue.push_back(pixels); + frameQueueMutex.unlock(); +#else +*/ + if (!usePixelSource) { + // this part from ofImage::saveScreen + //int screenHeight = ofGetViewportHeight(); // if we are in a FBO or other viewport, this fails: ofGetHeight(); + //int screenY = screenHeight - posY; + //screenY -= inH; // top, bottom issues + + //glReadPixels(posX, screenY, inW, inH, GL_RGB, GL_UNSIGNED_BYTE, inPixels); + } + else + { + memcpy(inPixels, pixelSource, inW * inH * 3); + } + encodeFrame(); +//#endif + //lastFrameTime = ofGetElapsedTimef(); + } + } + + void ofxMovieExporter::encodeFrame() + { + + avpicture_fill((AVPicture*)inFrame, inPixels, PIX_FMT_RGB24, inW, inH); + avpicture_fill((AVPicture*)outFrame, outPixels, PIX_FMT_YUV420P, outW, outH); + + // intentionally flip the image to compensate for OF flipping if reading from the screen + if (!usePixelSource) + { + inFrame->data[0] += inFrame->linesize[0] * (inH - 1); + inFrame->linesize[0] = -inFrame->linesize[0]; + } + + //perform the conversion for RGB to YUV and size + sws_scale(convertCtx, inFrame->data, inFrame->linesize, 0, inH, outFrame->data, outFrame->linesize); + + int outSize = avcodec_encode_video(codecCtx, encodedBuf, ENCODED_FRAME_BUFFER_SIZE, outFrame); + if (outSize > 0) + { + AVPacket pkt; + av_init_packet(&pkt); + //pkt.pts = av_rescale_q(codecCtx->coded_frame->pts, codecCtx->time_base, videoStream->time_base); + //if(codecCtx->coded_frame->key_frame) pkt.flags |= AV_PKT_FLAG_KEY; + pkt.pts = frameNum;//ofGetFrameNum();//codecCtx->coded_frame->pts; + pkt.flags |= AV_PKT_FLAG_KEY; + pkt.dts = pkt.pts; + pkt.stream_index = videoStream->index; + pkt.data = encodedBuf; + pkt.size = outSize; + av_write_frame(formatCtx, &pkt); + } + frameNum++; + } + + void ofxMovieExporter::allocateMemory() + { + // clear if we need to reallocate + if(inPixels) + clearMemory(); +/* + // allocate input stuff +#ifdef _THREAD_CAPTURE + //unsigned char* initFrameMem = new unsigned char[inW * inH * 3 * INIT_QUEUE_SIZE]; + for (int i = 0; i < INIT_QUEUE_SIZE; i++) + { + //frameMem.push_back(initFrameMem + inW * inH * 3 * i); + frameMem.push_back(new unsigned char[inW * inH * 3]); + } +#else +*/ + inPixels = new unsigned char[inW * inH * 3]; +//#endif + inFrame = avcodec_alloc_frame(); + + // allocate output stuff + int outSize = avpicture_get_size(PIX_FMT_YUV420P, outW, outH); + outPixels = (unsigned char*)av_malloc(outSize); + outFrame = avcodec_alloc_frame(); + + encodedBuf = (unsigned char*)av_malloc(ENCODED_FRAME_BUFFER_SIZE); + } + + void ofxMovieExporter::clearMemory() { +/* + // clear input stuff +#ifdef _THREAD_CAPTURE + for (int i = 0; i < frameMem.size(); i++) + { + delete[] frameMem[i]; + } + for (int i = 0; i < frameQueue.size(); i++) + { + delete[] frameQueue[i]; + } +#else +*/ + delete[] inPixels; +//#endif + + inPixels = NULL; + + av_free(inFrame); + av_free(outFrame); + av_free(encodedBuf); + av_free(outPixels); + + inFrame = NULL; + outFrame = NULL; + encodedBuf = NULL; + outPixels = NULL; + } + + void ofxMovieExporter::initEncoder() + { + ///////////////////////////////////////////////////////////// + // find codec + codec = avcodec_find_encoder(codecId); + //if (!codec) ofLog(OF_LOG_ERROR, "ofxMovieExporter: Codec not found"); + + //////////////////////////////////////////////////////////// + // auto detect the output format from the name. default is mpeg. + ostringstream oss; + oss << "amovie." << container; + outputFormat = av_guess_format(NULL, oss.str().c_str(), NULL); + //if (!outputFormat) ofLog(OF_LOG_ERROR, "ofxMovieExporter: Could not guess output container for an %s file (ueuur!!)", container.c_str()); + // set the format codec (the format also has a default codec that can be read from it) + outputFormat->video_codec = codec->id; + + ///////////////////////////////////////////////////////////// + // allocate the format context + formatCtx = avformat_alloc_context(); + //if (!formatCtx) ofLog(OF_LOG_ERROR, "ofxMovieExporter: Could not allocate format context"); + formatCtx->oformat = outputFormat; + + ///////////////////////////////////////////////////////////// + // set up the video stream + videoStream = av_new_stream(formatCtx, 0); + + ///////////////////////////////////////////////////////////// + // init codec context + codecCtx = videoStream->codec; + codecCtx->bit_rate = bitRate; + codecCtx->width = outW; + codecCtx->height = outH; + + codecCtx->time_base.num = 1;//codecCtx->ticks_per_frame; + codecCtx->time_base.den = frameRate; + videoStream->time_base = codecCtx->time_base; + + + codecCtx->gop_size = 10; /* emit one intra frame every ten frames */ + codecCtx->pix_fmt = PIX_FMT_YUV420P; + + if (codecCtx->codec_id == CODEC_ID_MPEG1VIDEO) + { + /* needed to avoid using macroblocks in which some coeffs overflow + this doesnt happen with normal video, it just happens here as the + motion of the chroma plane doesnt match the luma plane */ + codecCtx->mb_decision=2; + } + // some formats want stream headers to be seperate + if(!strcmp(formatCtx->oformat->name, "mp4") || !strcmp(formatCtx->oformat->name, "mov") || !strcmp(formatCtx->oformat->name, "3gp")) + codecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER; + + // set the output parameters (must be done even if no parameters). + //if ( + // + // + // ??????? + + //av_set_parameters(formatCtx, NULL); + // + // + // + // + + // < 0) ofLog(OF_LOG_ERROR, "ofxMovieExproter: Could not set format parameters"); + + AVDictionary *options; //= NULL; causes a forward declaration error!? + options=NULL; + // open codec + //if ( + avcodec_open2(codecCtx, codec,&options); + // < 0) ofLog(OF_LOG_ERROR, "ofxMovieExproter: Could not open codec"); + } +} diff --git a/rotord/ofxMovieExporter.h b/rotord/ofxMovieExporter.h new file mode 100644 index 0000000..ae6b306 --- /dev/null +++ b/rotord/ofxMovieExporter.h @@ -0,0 +1,209 @@ +/* + * ofxMovieExporter.h + * + * Copyright (c) 2011, Neil Mendoza, http://www.neilmendoza.com + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of 16b.it nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +//#pragma once + +#include <unordered_map> +#include <deque> +#include <math.h> +#include <memory> + +#include "Poco/Net/HTTPServer.h" +#include "Poco/Net/HTTPResponse.h" +#include "Poco/UUID.h" +#include "Poco/UUIDGenerator.h" +#include "Poco/Notification.h" +#include "Poco/NotificationCenter.h" +#include "Poco/Observer.h" +#include "Poco/ThreadPool.h" +#include "Poco/Thread.h" +#include "Poco/Task.h" +#include "Poco/Runnable.h" +#include "Poco/Mutex.h" +#include "Poco/Random.h" +#include "Poco/AutoPtr.h" +#include "Poco/File.h" +#include "xmlIO.h" +//#define _THREAD_CAPTURE +#include <string> +#include <iostream> +/* +#include "ofMain.h" + +// needed for gcc on win +#ifdef TARGET_WIN32 + #ifndef INT64_C + #define INT64_C(c) (c ## LL) + #define UINT64_C(c) (c ## ULL) + #endif +#endif +*/ +//#define UINT64_C(c) (c ## ULL) + + +extern "C" +{ + //needed both + #include <libavcodec/avcodec.h> + #include <libavformat/avformat.h> + #include <libavutil/mathematics.h> + //needed ofxMovieExporter + #include <libswscale/swscale.h> + //rest needed audio loader + #include <libavutil/opt.h> + #include <libavutil/channel_layout.h> + #include <libavutil/common.h> + #include <libavutil/imgutils.h> + + #include <libavutil/samplefmt.h> + + #include <libavutil/dict.h> + //#include <libavutil/dict.c> stops the compiler error but causes a linker error. does libavcodec need to be statically linked? + //#include <libavutil/imgutils.h> + //#include <libavutil/samplefmt.h> + //#include <libavutil/timestamp.h> +} + +namespace itg +{ + class ofxMovieExporter +//#ifdef _THREAD_CAPTURE +// : public ofThread +//#endif + { + public: + static const int ENCODED_FRAME_BUFFER_SIZE = 500000; + // defaults + static const int BIT_RATE = 4000000; + static const int FRAME_RATE = 25; + static const int OUT_W = 640; + static const int OUT_H = 480; + static const int INIT_QUEUE_SIZE = 50; + static const AVCodecID CODEC_ID = CODEC_ID_MPEG4; + static const std::string FILENAME_PREFIX; + static const std::string CONTAINER; + + ofxMovieExporter(); + ~ofxMovieExporter(); + // tested so far with... + // codecId = CODEC_ID_MPEG4, container = "mp4" + // codecId = CODEC_ID_MPEG2VIDEO, container = "mov" + void setup(int outW = OUT_W, int outH = OUT_H, int bitRate = BIT_RATE, int frameRate = FRAME_RATE, AVCodecID codecId = CODEC_ID, std::string container = CONTAINER); + void record(std::string filePrefix=FILENAME_PREFIX, std::string folderPath=""); + void stop(); + bool isRecording() const; + + // set the recording area + // x, y is the upper left corner of the recording area, default: 0, 0 + // w x h is the area size, default: viewport width x height + void setRecordingArea(int x, int y, int w, int h); + void setRecordingArea(int rect); + + // reset the recording area to the size of the current viewport (screen, FBO, etc) + void resetRecordingArea(); + + // get the recording area as a rectangle + int getRecordingArea(); + + // set an external pixel source, assumes 3 Byte RGB + // also sets the recording size but does not crop to the recording area + void setPixelSource(unsigned char* pixels, int w, int h); + + // reset the pixel source and record from the screen + // also resets the recording size to the viewport width + void resetPixelSource(); + + // get the number files that have been captured so far + int getNumCaptures(); + + // reset the filename counter back to 0 + void resetNumCaptures(); + + // get the recording size + inline int getRecordingWidth() {return outW;} + inline int getRecordingHeight() {return outH;} + + private: +//#ifdef _THREAD_CAPTURE +// void threadedFunction(); +// deque<unsigned char*> frameQueue; +// deque<unsigned char*> frameMem; +// ofMutex frameQueueMutex; +// ofMutex frameMemMutex; +//#endif + void initEncoder(); + void allocateMemory(); + void clearMemory(); + + void checkFrame();//ofEventArgs& args); + void encodeFrame(); + void finishRecord(); + + std::string container; + AVCodecID codecId; + + bool recording; + int numCaptures; + int frameRate; + int bitRate; + float frameInterval; + float lastFrameTime; + int frameNum; + std::string outFileName; + + AVOutputFormat* outputFormat; + AVFormatContext* formatCtx; + AVStream* videoStream; + + AVCodec* codec; + AVCodecContext* codecCtx; + + SwsContext* convertCtx; + + unsigned char* inPixels; + unsigned char* outPixels; + unsigned char* encodedBuf; + + AVFrame* inFrame; + AVFrame* outFrame; + + int posX, posY; + int inW, inH; + int outW, outH; + + bool usePixelSource; + unsigned char* pixelSource; + }; + + inline bool ofxMovieExporter::isRecording() const { return recording; } +} + +namespace Apex = itg; diff --git a/rotord/renderContext.h b/rotord/renderContext.h new file mode 100755 index 0000000..e69de29 --- /dev/null +++ b/rotord/renderContext.h diff --git a/rotord/rotor.h b/rotord/rotor.h index 9f0c2e3..9728909 100755 --- a/rotord/rotor.h +++ b/rotord/rotor.h @@ -71,7 +71,7 @@ using Poco::UUID; using Poco::UUIDGenerator; using Poco::Net::HTTPResponse; - +/* extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> @@ -88,6 +88,9 @@ extern "C" { #include <libavutil/samplefmt.h> //#include <libavutil/timestamp.h> } +*/ + +#include "ofxMovieExporter.h" #define AUDIO_INBUF_SIZE 20480 #define AUDIO_REFILL_THRESH 4096 |
