diff options
| author | Tim Redfern <tim@herge.(none)> | 2013-03-26 18:33:54 +0000 |
|---|---|---|
| committer | Tim Redfern <tim@herge.(none)> | 2013-03-26 18:33:54 +0000 |
| commit | 7cf0551b85deed68d0eefd51e7b233e5c488f63b (patch) | |
| tree | 651b793a986bc847b954d05d1d87c5a07653cb05 | |
| parent | 1cd4f29bbb6a1f79bc75ca345e28b0fd4a86f20d (diff) | |
nearly working thumbnailer
| -rw-r--r-- | rotord/rotor.cpp | 181 | ||||
| -rwxr-xr-x | rotord/rotor.h | 77 | ||||
| -rw-r--r-- | rotord/rotord.cbp | 2 |
3 files changed, 124 insertions, 136 deletions
diff --git a/rotord/rotor.cpp b/rotord/rotor.cpp index de9f4ea..85382ea 100644 --- a/rotord/rotor.cpp +++ b/rotord/rotor.cpp @@ -6,7 +6,7 @@ bool fequal(const float u,const float v){ if (abs(u-v)<.001) return true; else return false; }; - + using namespace Rotor; @@ -17,7 +17,7 @@ using namespace Rotor; string output=""; int outputNo=0; */ - + void Render_context::runTask() { while (!isCancelled()) { int cmd=0; @@ -32,7 +32,7 @@ void Render_context::runTask() { //audio_analyser.process(audio_filename); //vampHost::runPlugin("","qm-vamp-plugins","qm-tempotracker", "",0, audio_filename, cerr,true); vector<base_audio_processor*> proc; - proc.push_back(new audio_thumbnailer()); + proc.push_back(audio_thumb); if (load_audio(audio_filename,proc)) { state=AUDIO_READY; //set the response @@ -83,8 +83,8 @@ Command_response Render_context::session_command(const std::vector<std::string>& //check file exists Poco::File f=Poco::File(command[3]); //std::auto_ptr<std::istream> pStr(URIStreamOpener::defaultOpener().open(command[3])); - - + + if (f.exists()) { //pass to worker thread ??if engine is ready?? ??what if engine has finished but results aren't read?? audio_filename=command[3]; //for now, store session variables in memory @@ -95,7 +95,7 @@ Command_response Render_context::session_command(const std::vector<std::string>& response.status=HTTPResponse::HTTP_NOT_FOUND; response.description="<status context='"+command[1]+"'>File "+command[3]+" not found</status>\n"; } - + } else { response.status=HTTPResponse::HTTP_BAD_REQUEST; @@ -108,24 +108,20 @@ Command_response Render_context::session_command(const std::vector<std::string>& response.status=HTTPResponse::HTTP_OK; response.description="<status context='"+command[1]+"'>Rotor: analysing audio</status>\n"; char c[20]; - sprintf(c,"%02f",audio_analyser.get_progress()); + sprintf(c,"%02f",progress); response.description+="<progress>"+string(c)+"</progress>\n"; } - if (state==AUDIO_READY) { - //not sure about this-- should this state be retained? - //can the data only be read once? - //for now - response.status=HTTPResponse::HTTP_OK; - response.description="<status context='"+command[1]+"'>Rotor: audio ready</status>\n"; - response.description+="<beats>"; - for (auto& i: audio_analyser.beats) { //is actually giving no data? - char c[20]; - sprintf(c,"%02f",i); - response.description+="<beat>"+string(c)+"</beat>"; - } - response.description+="\n</beats>"; - state=IDLE; - } + if (state==AUDIO_READY) { + //not sure about this-- should this state be retained? + //can the data only be read once? + //for now + response.status=HTTPResponse::HTTP_OK; + response.description="<status context='"+command[1]+"'>Rotor: audio ready</status>\n"; + response.description+="<audio>\n"; + response.description+=audio_thumb->print(); + response.description+="\n</audio>"; + state=IDLE; + } } if (command[0]=="DELETE") { //for now @@ -151,7 +147,7 @@ Command_response Render_context::session_command(const std::vector<std::string>& if (state==IDLE) { //eventually not like this Poco::File f=Poco::File(command[3]); if (f.exists()) { - string graph_filename=command[3]; + string graph_filename=command[3]; if (load_graph(graph_filename)) { response.status=HTTPResponse::HTTP_OK; //response.description="<status context='"+command[1]+"'>Rotor: loaded graph "+command[3]+"</status>\n"; @@ -241,7 +237,7 @@ Command_response Render_context::session_command(const std::vector<std::string>& response.description="<status>DUMMY RESPONSE 1</status>\n"; response.status=HTTPResponse::HTTP_OK; } - + } if (command[2]=="render") { if (command[0]=="GET") { @@ -281,22 +277,22 @@ bool Render_context::load_audio(const string &filename,vector<base_audio_process //in the case of audio analysis, the daemon will pass each audio analysis object each chunk of data as it gets it //there could even be an array of audio analysis functions to perform simultaneously? //how about a vector of objects that subclass the base audio processor class? - + //1st get parameters and initialise the processors //then begin data loop locking progress variable after each frame - + // // //the example in ffmpeg works, but it isn't one that identifies a codec- it is hard coded to look for a codec for AV_CODEC_ID_MP2 //it also doesn't load through libavformat - which opens containers- it just loads a naked .mp2 stream // // - + /* av_register_all(); - + std::shared_ptr<AVFormatContext> avFormat(avformat_alloc_context(), &avformat_free_context); - + auto avFormatPtr = avFormat.get(); if (avformat_open_input(&avFormatPtr,filename.c_str(),nullptr, nullptr) != 0) { cerr <<"Rotor: Error while calling avformat_open_input (probably invalid file format)" << endl; @@ -307,11 +303,11 @@ bool Render_context::load_audio(const string &filename,vector<base_audio_process cerr << "Rotor: Error while calling avformat_find_stream_info" << endl; return false; } - + av_dump_format(avFormat.get(), 0, 0, false); //avformat.h line 1256 - - + + AVStream* stream = nullptr; for (unsigned int i = 0; i < avFormat->nb_streams; ++i) { if (avFormat->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -324,16 +320,16 @@ bool Render_context::load_audio(const string &filename,vector<base_audio_process cerr <<"Rotor: Didn't find any audio stream in the file"<< endl; return false; } - + // getting the required codec structure const auto codec = avcodec_find_decoder(stream->codec->codec_id); //returns AVCodec* if (codec == nullptr) { cerr <<"Rotor: Audio codec not available"<< endl; return false; } - - //AVCodecContext?? avFormat->streams[i]->codec - + + //AVCodecContext?? avFormat->streams[i]->codec + // allocating a structure std::shared_ptr<AVCodecContext> audioCodec(avcodec_alloc_context3(codec), [](AVCodecContext* c) { avcodec_close(c); av_free(c); }); @@ -342,7 +338,7 @@ bool Render_context::load_audio(const string &filename,vector<base_audio_process // make sure that this vector exists as long as the avVideoCodec exists std::vector<uint8_t> codecContextExtraData; codecContextExtraData.assign(stream->codec->extradata, stream->codec->extradata + stream->codec->extradata_size); - + audioCodec->extradata = reinterpret_cast<uint8_t*>(codecContextExtraData.data()); audioCodec->extradata_size = codecContextExtraData.size(); @@ -351,23 +347,23 @@ bool Render_context::load_audio(const string &filename,vector<base_audio_process cerr <<"Rotor: Could not open codec"<< endl; return false; } - - + + //avcodec.h line 1026 - - + + Packet packet; // decoding code here //cerr << "audio codec context - sample rate: "<< audioCodec->sample_rate <<", channels: "<<audioCodec->channels<<", sample format: "<<audioCodec->sample_fmt<<endl; - + // suspect why was this in twice 210313 //do { // if (packet.packet.stream_index != stream->index) /// continue; // the packet is not about the stream we want, let's jump again the start of the loop //} while(0); // - + // allocating an AVFrame std::shared_ptr<AVFrame> avFrame(avcodec_alloc_frame(), &av_free); @@ -375,10 +371,10 @@ bool Render_context::load_audio(const string &filename,vector<base_audio_process //Packet packet; // data in the packet of data already processed size_t offsetInData = 0; - - + + bool foundPacket=false; - + // the decoding loop, running until EOF while (true) { // reading a packet using libavformat @@ -392,20 +388,20 @@ bool Render_context::load_audio(const string &filename,vector<base_audio_process cerr << "audio codec context - sample rate: "<< audioCodec->sample_rate <<", channels: "<<audioCodec->channels<<", sample format: "<<audioCodec->sample_fmt<<endl; foundPacket=true; } - + } - - + + // preparing the packet that we will send to libavcodec AVPacket packetToSend; packetToSend.data = packet.packet.data + offsetInData; packetToSend.size = packet.packet.size - offsetInData; - + // sending data to libavcodec int isFrameAvailable = 0; //const auto processedLength = avcodec_decode_video2(avVideoCodec.get(), avFrame.get(), &isFrameAvailable, &packetToSend); - + const auto processedLength = avcodec_decode_audio4(audioCodec.get(), avFrame.get(), &isFrameAvailable, &packetToSend); //utils.c line 2018 @@ -414,16 +410,16 @@ bool Render_context::load_audio(const string &filename,vector<base_audio_process //unsigned int numberOfSamplesContainedInTheFrame = stream.frame->nb_samples * stream.codecContext->channels; //unsigned int numberOfSamplesToPlayPerSecond = stream.codecContext->sample_rate; // usually 44100 or 48000 //const void* data = stream.frame->data[0]; - - + + if (processedLength < 0) { //av_free_packet(&packet); shouldn't have to because of type safe wrapper cerr <<"Error "<<processedLength<< " while processing the data"<< endl; return false; } offsetInData += processedLength; - - + + // processing the image if available if (isFrameAvailable) { // display image on the screen @@ -433,8 +429,8 @@ bool Render_context::load_audio(const string &filename,vector<base_audio_process std::this_thread::sleep(std::chrono::milliseconds(msToWait)); } */ - - + + http://www.gamedev.net/topic/624876-how-to-read-an-audio-file-with-ffmpeg-in-c/ // Initialize FFmpeg av_register_all(); @@ -455,7 +451,7 @@ bool Render_context::load_audio(const string &filename,vector<base_audio_process std::cout << "Error opening the file" << std::endl; return false; } - + if (avformat_find_stream_info(formatContext, NULL) < 0) { @@ -504,22 +500,20 @@ bool Render_context::load_audio(const string &filename,vector<base_audio_process av_dump_format(formatContext, 0, 0, false); //avformat.h line 1256 int samples = ((formatContext->duration + 5000)*codecContext->sample_rate)/AV_TIME_BASE; - + std::cout << "This stream has " << codecContext->channels << " channels, a sample rate of " << codecContext->sample_rate << "Hz and "<<samples <<" samples" << std::endl; std::cout << "The data is in format " <<codecContext->sample_fmt<< " (aka "<< av_get_sample_fmt_name(codecContext->sample_fmt) << ") "<<std::endl; - + //we can now tell the processors the format //we can work out the number of samples at this point - - - - + for (auto p: processors) { p->init(codecContext->channels,16,samples); } AVPacket packet; av_init_packet(&packet); + int sample_processed=0; // Read the packets in a loop while (av_read_frame(formatContext, &packet) == 0) @@ -541,18 +535,19 @@ bool Render_context::load_audio(const string &filename,vector<base_audio_process // the audio data, so I won't add any junk here that might confuse you. Typically, if I want to find // documentation on an FFmpeg structure or function, I just type "<name> doxygen" into google (like // "AVFrame doxygen" for AVFrame's docs) - + //std::cout << "Got a frame: bytes " << bytes << std::endl; //now we can pass the data to the processor(s) for (auto p: processors) { - p->process_frame(frame->data[0],frame->nb_samples); + sample_processed=p->process_frame(frame->data[0],frame->nb_samples); } - + mutex.lock(); + progress=((double)sample_processed)/samples; + mutex.unlock(); } } - // You *must* call av_free_packet() after each call to av_read_frame() or else you'll leak memory - av_free_packet(&packet); + av_free_packet(&packet); //crashes here!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! SIGSEV In _int_free (av=0xb4600010, p=0xb46025c8, have_lock=0) at malloc.c:4085 () } // Some codecs will cause frames to be buffered up in the decoding process. If the CODEC_CAP_DELAY flag @@ -568,6 +563,9 @@ bool Render_context::load_audio(const string &filename,vector<base_audio_process for (auto p: processors) { p->process_frame(frame->data[0],frame->nb_samples); } + mutex.lock(); + progress=((double)sample_processed)/samples; + mutex.unlock(); } } @@ -575,9 +573,6 @@ bool Render_context::load_audio(const string &filename,vector<base_audio_process av_free(frame); avcodec_close(codecContext); av_close_input_file(formatContext); - - - return true; } @@ -614,7 +609,7 @@ bool Render_context::load_graph(string &filename){ } else cerr << "Rotor: linked input " << i2 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; } - else cerr << "Rotor: linking input " << i2 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl; + else cerr << "Rotor: linking input " << i2 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl; } xml.popTag(); } @@ -626,44 +621,6 @@ bool Render_context::load_graph(string &filename){ } xml.popTag(); } - /* - model_name=XML.getAttribute("map4","model","none",0); - model_x=ofToFloat(XML.getAttribute("map4","x","none",0)); - model_y=ofToFloat(XML.getAttribute("map4","y","none",0)); - model_z=ofToFloat(XML.getAttribute("map4","z","none",0)); - if(XML.pushTag("map4")) { - numViews=XML.getNumTags("view"); - if(numViews) { - views=new viewpoint[numViews]; - for (int i=0;i<numViews;i++){ - XML.pushTag("view",i); - vector<string>keys; - XML.getAttributeNames("settings", keys, 0); - map<string,string>settings; - for (int k=0;k<keys.size();k++) { - settings[keys[k]]=XML.getAttribute("settings",keys[k],"none",0); - } - views[i].setup(settings); - XML.popTag(); - } - } - - numClips=XML.getNumTags("clip"); - if (numClips) { - clips=new string[numClips]; - for (int i=0;i< numClips;i++) { - XML.pushTag("clip",i); - clips[i]=XML.getAttribute("settings","file","none",0); - XML.popTag(); - } - } - - XML.popTag(); - for (int i=0;i< numClips;i++) { - printf("clip: %s\n",clips[i].c_str()); - } - } - */ return true; } else return false; @@ -675,4 +632,4 @@ Node_factory::Node_factory(){ add_type("divide",new Signal_divide()); add_type("==",new Is_new_integer()); add_type("signal_output",new Signal_output()); -}
\ No newline at end of file +} diff --git a/rotord/rotor.h b/rotord/rotor.h index e16520b..de8e686 100755 --- a/rotord/rotor.h +++ b/rotord/rotor.h @@ -335,7 +335,7 @@ namespace Rotor { }; class base_audio_processor { public: - virtual bool process_frame(uint8_t *data,int samples)=0; + virtual int process_frame(uint8_t *data,int samples)=0; void init(int _channels,int _bits,int _samples) { channels=_channels; bits=_bits; @@ -350,7 +350,7 @@ namespace Rotor { public: audio_thumbnailer(){ height=64; - width=512; + width=128; //fit data=new uint8_t[height*width]; memset(data,0,height*width); } @@ -360,39 +360,66 @@ namespace Rotor { void init(int _channels,int _bits,int _samples) { base_audio_processor::init(_channels,_bits,_samples); samples_per_column=samples/width; - stored_data=new uint8_t[samples_per_column*channels*(bits>>3)]; - sample=0; + column=0; //point thumbnail bitmap + out_sample=0; //sample in whole track + offset=(int)(pow(2.0,bits)/2.0); + scale=1.0/offset; } - bool process_frame(uint8_t *data,int samples_in_frame){ + int 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 next_sample=0; - uint64_t accum; - while (next_sample<samples_in_frame) { - while (sample<samples_in_column&&next_sample<samples_in_frame) { - //do the accumulation + int in_sample=0; + while (in_sample<samples_in_frame&&column<width) { + int sample=0; + int samples=0; + double accum=0; + while (sample<samples_per_column&&in_sample<samples_in_frame) { + //accumulate squares for this column for (int i=0;i<channels;i++) { int this_val=0; for (int j=0;j<bytes;j++) { this_val+=data[(sample*stride)+(i*bytes)+j]<<j; } - accum+=this_val*this_val; - next_sample++; - sample++; - } + //convert from integer data format - i.e s16p - to audio signal in -1..1 range + //don't know how many bytes we are dealing with necessarily? + double val=(this_val-offset)*scale; + accum+=val*val; + samples++; } - } do; - - } do; - return true; + in_sample++; + sample++; + out_sample++; + } + //get root-mean + double mean=pow(accum/samples,0.5); + 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++; + } + return out_sample; + } + string print(){ + string 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"; + } + return output; } uint8_t *data; - uint8_t *stored_data; int height,width,samples_per_column; - int sample; + int column,out_sample; + //for drawing graph + int offset; + double scale; }; class Render_context: public Poco::Task { //Poco task object //manages a 'patchbay' @@ -400,7 +427,8 @@ namespace Rotor { //and low level interface onto the graph public: Render_context(const std::string& name): Task(name) { - state=IDLE; + audio_thumb=new audio_thumbnailer(); + state=IDLE; }; void runTask(); void add_queue(int item); @@ -417,11 +445,12 @@ namespace Rotor { private: int state; - float progress; //for a locking process: audio analysis or rendering + double progress; //for a locking process: audio analysis or rendering //thread only does one thing at once std::deque<int> work_queue; Poco::Mutex mutex; //lock for access from parent thread std::string audio_filename; + audio_thumbnailer *audio_thumb; vampHost::QMAnalyser audio_analyser; xmlIO xml; Graph graph; @@ -431,6 +460,6 @@ namespace Rotor { /* coding style -Types begin with a capital, use underscore as a seperator -instances begin with a small letter +Types begin with capitals 'CamelCase' +variables/ instances use lower case with underscore as a seperator */ diff --git a/rotord/rotord.cbp b/rotord/rotord.cbp index 4f964e9..f4eafc3 100644 --- a/rotord/rotord.cbp +++ b/rotord/rotord.cbp @@ -49,6 +49,8 @@ <Add option="-Wall" /> </Compiler> <Unit filename="Makefile" /> + <Unit filename="avCodec.cpp" /> + <Unit filename="avCodec.h" /> <Unit filename="ofUtils.cpp" /> <Unit filename="ofUtils.h" /> <Unit filename="rotor.cpp" /> |
