diff options
Diffstat (limited to 'rotord/src/rotor.cpp')
| -rwxr-xr-x | rotord/src/rotor.cpp | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/rotord/src/rotor.cpp b/rotord/src/rotor.cpp new file mode 100755 index 0000000..8b72c50 --- /dev/null +++ b/rotord/src/rotor.cpp @@ -0,0 +1,361 @@ +#include "rotor.h" +#include "nodes_audio_analysis.h" +#include "nodes_drawing.h" + +using namespace Rotor; +Node_factory::Node_factory(){ + //for now, statically load prototype map in constructor + add_type("audio_analysis",new Audio_analysis()); + add_type("divide",new Signal_divide()); + add_type("bang",new Is_new_integer()); + add_type("signal_output",new Signal_output()); + add_type("testcard",new Testcard()); + add_type("video_output",new Video_output()); + add_type("video_loader",new Video_loader()); + add_type("on_off",new On_off()); + add_type("invert",new Invert()); + add_type("video_cycler",new Video_cycler()); + add_type("luma_levels",new Luma_levels()); + add_type("echo_trails",new Echo_trails()); + add_type("time",new Time()); + add_type("track_time",new Track_time()); + add_type("comparison",new Comparison()); //TODO: alias to symbols + add_type("arithmetic",new Arithmetic()); //TODO: alias to symbols + add_type("signal_colour",new Signal_colour()); + add_type("signal_greyscale",new Signal_greyscale()); + add_type("image_arithmetic",new Image_arithmetic()); + add_type("random",new Random()); + add_type("blend",new Blend()); + add_type("mirror",new Mirror()); + add_type("monochrome",new Monochrome()); + add_type("transform",new Transform()); + add_type("alpha_merge",new Alpha_merge()); + add_type("draw",new Draw()); +} + +bool Signal_input::connect(Signal_node* source) { + if (source->output_type=="signal") { + connection=(Node*)source; + return true; + } + else return false; +} +void Parameter_input::update(const Time_spec& time){ //gets input and updates variable + if (receiver){ + *receiver=((Signal_node*)connection)->get_output(time); + } +} +bool Image_input::connect(Image_node* source) { + if (source->output_type=="image") { + connection=(Node*)source; + return true; + } + else return false; +} +void Node::update_params(const Time_spec& time){ //compute connected parameters + for (auto p:parameter_inputs){ + p->update(time); + } +} +bool Signal_output::render(const float duration, const float framerate,string &xml_out){ + //testing signal routes + cerr << "Rotor: Signal_output rendering " << duration << " seconds at " << framerate << " frames per second" << endl; + float step=1.0f/framerate; + float v=0.0f; + float min=10000000.0f; + float max=-10000000.0f; + for (float f=0.0f;f<duration;f+=step) { + float u=get_output(Time_spec(f,framerate,duration)); + if (!fequal(u,v)) { + xml_out+=("<signal time='"+ofToString(f)+"'>"+ofToString(u)+"</signal>\n"); + v=u; + if (v>max) max=v; + if (v<min) min=v; + } + } + xml_out+=("<signal_finished min='"+ofToString(min)+"' max='"+ofToString(max)+"'/>\n"); + return true; +} + +bool Audio_thumbnailer::init(int _channels,int _bits,int _samples,int _rate) { + //base_audio_processor::init(_channels,_bits,_samples); + channels=_channels; + bits=_bits; + samples=_samples; + samples_per_column=samples/width; + column=0; //point thumbnail bitmap + out_sample=0; //sample in whole track + offset=0x1<<(bits-1); //signed audio + scale=1.0/offset; + sample=0; + samples=0; + accum=0.0; + return true; +} +int Audio_thumbnailer::process_frame(uint8_t *_data,int samples_in_frame){ + //begin by processing remaining samples + //samples per column could be larger than a frame! (probably is) + //but all we are doing is averaging + int bytes=(bits>>3); + int stride=channels*bytes; + int in_sample=0; + while (in_sample<samples_in_frame&&column<width) { + //continue the column + while (sample<samples_per_column&&in_sample<samples_in_frame) { + //accumulate samples for this column until we run out of samples + for (int i=0;i<channels;i++) { + unsigned int this_val=0; + for (int j=0;j<bytes;j++) { + this_val+=_data[(in_sample*stride)+(i*bytes)+j]<<(j*8); + } + //convert from integer data format - i.e s16p - to audio signal in -1..1 range + //presume 16 bits for now... + double val=((double)((int16_t)this_val))*scale; + accum+=val*val; + samples++; + } + in_sample++; + sample++; + out_sample++; + } + if (sample==samples_per_column) { //finished a column + //get root-mean + double mean=pow(accum/samples,0.5); + //if (column==0) { + // cerr << "first column total: "<< accum << " in " << samples << " samples, average " << (accum/samples)<<endl; + //} + int colheight=height*mean*0.5; + int hh=height>>1; + for (int i=0;i<height;i++) { + data[i*width+column]=abs(i-hh)<colheight?0xff:0x00; + } + column++; + sample=0; + samples=0; + accum=0.0; + } + } + return out_sample; +} +string Audio_thumbnailer::print(){ + //base64 encode the image data output it + + stringstream output; + Poco::Base64Encoder *enc=new Poco::Base64Encoder(output); + + enc->write((char*)data,width*height); + //tring output; + /* + for (int j=0;j<height;j++) { + for (int i=0;i<width;i++) { + output+=data[j*width+i]<0x7f?"0":"1"; + } + output +="\n"; + } + */ + enc->close(); + delete enc; + return output.str(); +} +bool Audio_analysis::init(int _channels,int _bits,int _samples, int _rate) { + //need these to make sense of data + channels=_channels; + bits=_bits; + samples=_samples; + + return analyser.init(soname,id,_channels,_bits,_samples,_rate,outputNo,params); + + + //attempt to load vamp plugin and prepare to receive frames of data + //should the audio analysis contain a vamphost or should it inherit? + //maybe neater to contain it in terms of headers etc + +} +int Audio_analysis::process_frame(uint8_t *data,int samples_in_frame) { + analyser.process_frame(data,samples_in_frame); + return 1; +} +void Audio_analysis::cleanup() { + analyser.cleanup(); + //print_features(); +} +void Audio_analysis::print_features(){ + for (auto i: analyser.features) { + cerr<<" ["<<i.second<<":"<<i.first<<"]"; + } + cerr<<endl; +} + +bool Video_output::render(const float duration, const float framerate,const string &output_filename,const string &audio_filename,float& progress,int outW,int outH){ + + // + //setup defaults + int bitRate=5000000; + AVCodecID codecId=AV_CODEC_ID_H264; //MPEG4; + std::string container ="mp4"; + + + //at the moment it crashes if you render before audio is loaded and also on 2nd render + libav::exporter exporter; + + float spct=100.0f/duration; + + if (exporter.setup(outW,outH,bitRate,framerate,container)) { //codecId, + if (exporter.record(output_filename)) { + + libav::audioloader audioloader; + + bool usingaudio=audioloader.setup(audio_filename); + + cerr << "Rotor: Video_output rendering " << duration << " seconds at " << framerate << " fps, audio frame size: " << exporter.get_audio_framesize()<<endl; + //25fps video and 43.06640625fps audio? hmm + //how to get the timecodes correct for the interleaved files + + struct timeval start, end; + + gettimeofday(&start, NULL); + + + float vstep=1.0f/framerate; + float v=0.0f; + float vf=0.0f; + float af=0.0f; + while (vf<duration){ //-vstep) { + if (usingaudio) { + while (!fless(af,vf)) { + //insert audio frames until we are ahead of the video + exporter.encodeFrame(audioloader.get_samples(exporter.get_audio_framesize())); + af+=exporter.get_audio_step(); + + } + } + + + //[mp3 @ 0x7fffe40330e0] max_analyze_duration 5000000 reached at 5015510 microseconds + //[mp3 @ 0x7fffe4033ec0] Insufficient thread locking around avcodec_open/close() + //[mp3 @ 0x7fffe40330e0] Estimating duration from bitrate, this may be inaccurate + //[libx264 @ 0x7fffe8003940] using cpu capabilities: MMX2 SSE2Fast SSSE3 FastShuffle SSE4.2 + //[libx264 @ 0x7fffe8003940] profile High, level 3.0 + //[libx264 @ 0x7fffe8003940] 264 - core 123 r2189 35cf912 - H.264/MPEG-4 AVC codec - Copyleft 2003-2012 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=12 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=10 keyint_min=1 scenecut=40 intra_refresh=0 rc_lookahead=10 rc=abr mbtree=1 bitrate=400 ratetol=1.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00 + //Assertion ff_avcodec_locked failed at libavcodec/utils.c:2967 + + //cerr<<"videoloader: "<<vf<<" seconds, vstep "<<vstep<<" ,asking for frame "<<((int)((vf*framerate)+0.5))<<endl; + + Image* i=get_output(Frame_spec(vf,framerate,duration,outW,outH)); + if (i) { + exporter.encodeFrame(i->RGBdata); + + } + vf+=vstep; + progress=vf/duration; + } + + exporter.finishRecord(); + + gettimeofday(&end, NULL); + + float mtime = ((end.tv_sec-start.tv_sec) + (end.tv_usec-start.tv_usec)/1000000.0) + 0.5; + + printf("Rotor Video_output: rendered in %02f seconds\n", mtime); + + if (usingaudio) audioloader.close(); + + return true; + } + } + + return false; +} + +bool Video_loader::load(const string &filename){ + /* + //gstreamer needs absolute paths ALWAYS + //string uri="file:///home/tim/workspace/rotor/rotord/"+filename; + Poco::Path path; + string uri="file://"+path.current()+filename; + //cerr << "video input: loading "<<uri<<endl; + if (player->loadMovie(uri)){ + player->play(); + player->setPaused(true); + player->setFrameByFrame(true); + player->update(); + cerr<<"Rotor::Video_loader: "<<filename<<", "<<player->getDuration()<<" seconds "<<", "<<player->getWidth()<<"x"<<player->getHeight()<<endl; + image->setup_fromRGB(player->getWidth(),player->getHeight(),(uint8_t*) player->getPixels()); + return true; + } + */ + if (isLoaded) { + player.cleanup(); ///should be in decoder class? + isLoaded=false; + } + Poco::Path path; + string uri="file://"+filename; + isLoaded=player.open(uri); + if (isLoaded){ + cerr<<"Rotor::Video_loader: "<<filename<<", "<<player.getNumberOfFrames()<<" frames "<<", "<<player.getWidth()<<"x"<<player.getHeight()<<endl; + return true; + } + cerr<<"Rotor::Video_loader: failed to load "<<filename<<endl; + return false; +} +Image* Video_loader::output(const Frame_spec &frame){ + //wonder about the actual mechanism used by gstreamer + //have to implment callback when seek is ready? + //presume gstreamer caches a loaded frame? + + + //deal with reolution: swscale from avcodec or put scaler in pipeline? + //can image node point to buffer in gst rather than copying the pixels? + + //to test using fp time to seek: need a short movie with synced audio + + //fix actual duration and audio file + //trace frame that is being read + /* + if (player->isLoaded()){ + //player->setPosition(frame.time); + int wanted=((int) (frame.time*frame.framerate))%(player->getTotalNumFrames()-2); //-2?? + player->setFrame(wanted); + //while (player->getCurrentFrame()!=wanted){ + // cerr << "seeking to "<<wanted<<" :"<<player->getCurrentFrame()<<endl; + //player->setFrame(wanted); + //player->update(); + // sleep(.001); + //} + player->update(); + image->RGBdata=player->getPixels(); //don't really know why this is needed every frame + //cerr<<"Video_loader: retrieving frame "<<((int) (frame.time*frame.framerate))<<endl; + return image; + } + */ + + if (isLoaded){ + int wanted=(((int) ((frame.time*frame.framerate)+0.5))%(player.getNumberOfFrames())); //+1 is necessary because 1st frame in a video is number 1? + + + //if (wanted==99){ + // cerr<<"videoloader: near the end"<<endl; + //} + + //cerr<<"videoloader: requesting frame "<<wanted<<endl; + //if (wanted==68) { + // int nothing=0; + //} + + if (!player.fetchFrame(frame.w,frame.h,wanted)) { //seek fail + cerr<<"Rotor: failed to seek frame"<<endl; + if (image.w>0) return ℑ //just return the previous frame if possible + else return nullptr; + }; + //cerr<<"Video_loader: setting up frame: lineoffset="<<(player.pFrameRGB->linesize[0]-(frame.w*3))<<endl; + image.setup_fromRGB(frame.w,frame.h,player.pFrameRGB->data[0],player.pFrameRGB->linesize[0]-(frame.w*3)); + return ℑ + } + + //confusingly, crashes with files not made with short files? + //seems to be on last frame? - returns nullptr - still tries to clone? + //can't really return 1st frame instead, should get # of frames right 1st? + //think about what echo trails does on the last frame + + return nullptr; +}; |
