Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | -- Implement Media Kit : add a 'native' audio-out back-end -- * keep the OSS back-end mothballed, with a tweak to IsAlmostFull(), might return to it later ; * add a 'native' (direct-to-Genode-mixer) back-end which does not rely on signals but polls stream.queued() instead ; * this is being used successfully with AC ; * still have to clean-up/commit a few missing classes yet for the Media Kit to compile ; -- TESTS -- * audio playback is fluid (no 'on-air blanks') with upperlimit_queue_threshold set to 64, on my T410 ; * stability was bad starting a couple months back, but seems to be good now, no more lock-ups for the last few days, after I fixed a mem leak in my vfs-ntfs code (coincidence ?) ; * there remains a few question marks yet (do "grep FIXME" to find them) ; |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
2dd1f3902da2ada7fc52a49a3d71bdd8 |
User & Date: | user 2023-03-15 10:44:40 |
Context
2023-03-20
| ||
18:22 | -- Fix semaphores -- * acquire_sem() was not calling genode_sem.down() but was instead busy looping (sending EAGAIN back and forth) on 'busy' semaphores ; * we now call down() properly, being careful to first release the giant kernel lock ; * this introduces a race though (what if the sem is deleted inbetween), will look into it soon-ish ; -- TESTS -- * a few months back, we would lock-up or at least slow down to a crawl due to the busy-looping-with-giant-lock-held crazyness ; * been testing this fix for quite a few weeks now, looking good ; check-in: 05b6c4490f user: user tags: trunk | |
2023-03-15
| ||
10:44 | -- Implement Media Kit : add a 'native' audio-out back-end -- * keep the OSS back-end mothballed, with a tweak to IsAlmostFull(), might return to it later ; * add a 'native' (direct-to-Genode-mixer) back-end which does not rely on signals but polls stream.queued() instead ; * this is being used successfully with AC ; * still have to clean-up/commit a few missing classes yet for the Media Kit to compile ; -- TESTS -- * audio playback is fluid (no 'on-air blanks') with upperlimit_queue_threshold set to 64, on my T410 ; * stability was bad starting a couple months back, but seems to be good now, no more lock-ups for the last few days, after I fixed a mem leak in my vfs-ntfs code (coincidence ?) ; * there remains a few question marks yet (do "grep FIXME" to find them) ; check-in: 2dd1f3902d user: user tags: trunk | |
2023-03-01
| ||
17:29 | -- clean-up: various minor tweaks -- * Jamrules: revert roles of NEEDLIBS / UNDEFS again, seems more compatible ; * BDirectory: comment out fcntl(...CLOEXEC..) to dodge "n/i" log warnings ; * logging tweaks ; * comment tweaks ; * ToDo's ; No functional change intended ; check-in: 314a522e76 user: user tags: trunk | |
Changes
Added genode-haiku/haiku-on-genode/kits/media/_bmediafile_Temp.cpp.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 | /* * Copyright 2022-2023, ttcoder * All rights reserved. Distributed under the terms of the MIT license. */ // base.lib.a #include <base/heap.h> #include <base/mutex.h> // libmedia.so #include "_ge_consumer_base.h" // must relocate the instantiation due to hai-os headers clashing with Genode headers... extern hog::ConsumerBase * instantiate_output_consumer( Genode::Env & env ); //xx clean this up xx /******************/ #include "_ge_AvDecoder.cpp" //#include "_ge_AudioIn.cpp" /******************/ //#pragma mark - class Audio_player::Main { private: // Data Genode::Mutex feedLock; // protect concurrent access of Audio_player data by the decoder/feeder thread and the client thread(s) Genode::Env & env; Genode::Heap alloc; //Genode::Signal_handler<Main> progress_dispatcher; hog::ConsumerBase * outputConsumer; // might be either a ConsumerNative or ConsumerOss hog::FramesProducer * libavDecoder; private: // Data -- accessed by BMediaFile... #if 0 class AdvancerThread : public Genode::Thread { public: AdvancerThread( Genode::Env & env, Audio_player::Main & hub ); void entry() override; public: // (!) Audio_player::Main & playerMain; }; AdvancerThread feederThread; bool threadNeedsJoining; #else thread_id feedThread; #endif bool feedTearDown; //bool feedPaused = false; //bool is_stopped = false; ///need something like this (but not quite this), to handle "decoder is exhausted" situation private: // Code void feedMixer(); void accumulateDecodedFrames(); public: Main( Genode::Env & env, Genode::String<256> path ); ~Main(); bool IsTearingDown() const; //void handle_progress(); void Advance(); void start();// void stop();// uint64_t progress();// const; uint64_t current_frame();// const; uint64_t duration();// const; uint64_t count_frames();// const; }; static status_t audio_out_entry( void * ptr ) { Genode::log( "Entering audio_out_entry with obj=", ptr, " and name: ", Genode::Thread::myself()->name(), " .........." ); if( nullptr == ptr ) { Genode::error( "NULL audio_out ptr" ); return -1; } auto obj = static_cast<Audio_player::Main*>( ptr ); // With this extra call we make sure the thread starts with *2* back-to-back calls // to Advance(), before we ever begin to snooze(): // obj->Advance(); while( false == obj->IsTearingDown() ) { obj->Advance(); #if 1 // Note: we sleep *without* holding the <feedLock> mutex, of course: snooze( hog::snooze_micros ); #endif } Genode::log( "-Leaving- audio_out_entry ..........thread name: ", Genode::Thread::myself()->name() ); return 0; } //#pragma mark - Audio_player::Main::Main( Genode::Env & _env, Genode::String<256> path///FIXME-2 ! ) : env( _env ) ,alloc( env.ram(), env.rm() ) //,progress_dispatcher( env.ep(), *this, &Main::handle_progress ) ,outputConsumer( nullptr ) ,libavDecoder( nullptr ) #if 0 ,feederThread( env, *this ) ,threadNeedsJoining( false ) #else ,feedThread( 0 ) #endif ,feedTearDown( false ) { GeTRACE( "audio_main.Ctor, path: ", path, " ... parent thread name: ", Genode::Thread::myself()->name() ); outputConsumer = instantiate_output_consumer( env ); outputConsumer->rl.log( "\n create-decoder (will throw if invalid file); " ); libavDecoder = new (&alloc) hog::FramesProducer( path ); outputConsumer->rl.log( " dump-info; " ); Genode::log( "file info: -----------" ); libavDecoder->PrintToStream( path.string() ); outputConsumer->rl.log( " audio_main.Ctor complete; " ); } Audio_player::Main::~Main() { Genode::log( "audio_main.DTOR" ); outputConsumer->rl.log( "\n audio_main.DTOR; " ); // T- (since this calls lock(), so must be done *before* the below lock()) stop(); // T+ (should only block waiting for thread once the above call has unlocked) outputConsumer->rl.log( " tearing *Down* 'feed' thread; " ); #if 0 if( threadNeedsJoining ) feederThread.join(); #else wait_for_thread( feedThread, NULL ); #endif // Be extra careful and lock here to prevent accessors being called while we delete ? // Though if someone's calling "delete BMediaFile" it wouldn't make sense that they also call accessors Genode::Mutex::Guard lock_guard( feedLock ); Genode::destroy( &alloc, libavDecoder ); libavDecoder = nullptr; //Genode::log("destroy done"); delete outputConsumer; outputConsumer = nullptr; //Genode::log("delete audio-out done"); } bool Audio_player::Main::Main::IsTearingDown() const { return feedTearDown; } void Audio_player::Main::Advance() { //xx no need for this one, get rid of it xx if( feedTearDown ) return; // goal: we want to go to sleep only after stashing decoded samples to the roof // and filling the audio queue to the highest tolerable threshold (i.e. strike // a balance between excessive latency and instable/choppy playback) feedMixer(); // 30 iterations is enough to fill our 256 KB fifo... better now with 30 ? ///FIXME-2: nope, makes no difference, still getting "impossible queue count"... seems harmless though ? for( int i = 0; i < 30; i++ ) // we don't want a (potentially buggy/infinite) loop, but we don't want to call this just once either, hence this arbitrary for(): accumulateDecodedFrames(); //log( " bmf.accumulated: ", libavDecoder->DecodedFrames().read_avail() ); // once more for good measure, in case a couple packets were consumed during decoding ? : //feedMixer(); } void Audio_player::Main::feedMixer() { Genode::Mutex::Guard lock_guard( feedLock ); if( feedTearDown ) return; //xx test "> AUDIO_OUT_PACKET_SIZE" instead ? Well ConsumeBuffer() already does just that if( libavDecoder->DecodedFrames().read_avail() > 0 ) { outputConsumer->ConsumeBuffer( libavDecoder->DecodedFrames() ); //outputConsumer->rl.Dump(); } } void Audio_player::Main::accumulateDecodedFrames() { Genode::Mutex::Guard lock_guard( feedLock ); if( feedTearDown ) return; if( libavDecoder->DecodedFrames().write_avail() <= hog::writeavail_threshold ) // return; // not enough room to fix an entire chunk, we're full // outputConsumer->rl.log( "\n prodbuffer; " ); // T0 // const int n = libavDecoder->ProduceBuffer( hog::AUDIO_OUT_PACKET_SIZE // aka outputConsumer.frame_size() ); outputConsumer->rl.log( " produced=" ); outputConsumer->rl.log( n ); outputConsumer->rl.log( " bytes; " ); if( n <= 0 ) { outputConsumer->rl.log( " ** zero production! flush previously produced.. ** " ); Genode::log( " * zero prod : set Tear Down ! *" ); ///FIXME-2: once the first stage is solved (all reliability issues), refactor some more, so that we keep decoding to the end, we must not tear down prematurely and discard the tail-end audio samples (which have been decoded by libAV but not yet sent to mixer) #if 1 // Seems the decoder blows up sometimes, when called beyond EoF -- as a temporary fix, do a brutal bail out and make sure we do NOT call ProduceBuffer() any more: feedTearDown = true; // +Halt() ? outputConsumer->rl.Dump();// #endif } } void Audio_player::Main::start() { GeTRACE( "main-audio.start" ); Genode::Mutex::Guard lock_guard( feedLock ); if( feedThread > 0 ) Genode::error( "audio.start() was already called ! (will create a redundant thread ?)" ); /* outputConsumer.start_timer(); // "prime the pump" ?: //handle_progress(); */ #if 0 feederThread.start(); threadNeedsJoining = true; #else // we need a pthread, not a "bare" Genode thread, so that we can execute libav's libc code: status_t st = -1; feedThread = spawn_thread( audio_out_entry, "audio_out_entry", 10, this ); if( feedThread > 0 ) st = resume_thread( feedThread ); if( st != B_OK ) Genode::error( "audio_out: could not spawn/resume thread" ); #endif } void Audio_player::Main::stop() { Genode::log( "main.stop()" ); feedTearDown = true; /**********/ Genode::Mutex::Guard lock_guard( feedLock ); //outputConsumer.stop_timer(); // tell the timer or sigh to no longer call our progress() hook Genode::log( "main.out.halt()" ); outputConsumer->Halt(); } uint64_t Audio_player::Main::progress()// const { GeTRACE( " bmf.main.progress" ); Genode::Mutex::Guard lock_guard( feedLock ); return libavDecoder ? libavDecoder->progress() : 0; } uint64_t Audio_player::Main::duration()// const { GeTRACE( " bmf.main.duration" ); Genode::Mutex::Guard lock_guard( feedLock ); return libavDecoder ? libavDecoder->Duration() : 0; } uint64_t Audio_player::Main::current_frame()// const { GeTRACE( " bmf.main.curframe" ); Genode::Mutex::Guard lock_guard( feedLock ); return libavDecoder ? libavDecoder->current_frame() : 0; }; uint64_t Audio_player::Main::count_frames()// const { GeTRACE( " bmf.main.count_frames" ); Genode::Mutex::Guard lock_guard( feedLock ); return libavDecoder ? libavDecoder->count_frames() : 0; }; |
Added genode-haiku/haiku-on-genode/kits/media/_ge_AudioOut_native.cpp.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 | /* * Copyright 2023, ttcoder * All rights reserved. Distributed under the terms of the MIT license. */ // Genode *native* back-end for: // BSoundPlayer // base.lib.a #include <base/attached_ram_dataspace.h> // member //#include <base/signal.h> // libmedia.so #include "_ge_consumer_base.h" // ancestor class hog::ConsumerNative : public ConsumerBase { // Consumes audio frames produced by FramesProducer, // sends them to Audio_out service (e.g. mixer) public: ConsumerNative( Genode::Env & env ); ~ConsumerNative(); // ConsumerBase bool IsAlmostFull() override; void ConsumeBuffer( Frame_data & frame_data ) override; void Halt() override; private: // Code void packageFloats( const float * const decoded_interleaved ); //void handle_audio_out_progress(); //size_t frame_size() const; private: // Data Genode::Attached_ram_dataspace writeBuf; Genode::Constructible<Audio_out::Connection> outChannels[NUM_CHANNELS]; //Genode::Signal_context_capability sigHandler; _d_ //Genode::Signal_handler<ConsumerNative> sigHandler;// progress_dispatcher; }; // work-around for Hai-OS's #defines (imported by MediaFile.cpp) interfering with "attached_ram_dataspace.h" (imported by this file)... /**********************/ hog::ConsumerBase * instantiate_output_consumer( Genode::Env & env ) { return new hog::ConsumerNative( env ); } /**********************/ hog::ConsumerNative::ConsumerNative( Genode::Env & env ) : ConsumerBase( env ) ,writeBuf( env.ram(), env.rm(), AUDIO_OUT_PACKET_SIZE ) //,sigHandler( env.ep(), *this, &hog::ConsumerNative::handle_audio_out_progress ) ,outChannels() { GeTRACE( "native_out.Ctor" ); // build channels for( int i = 0; i < NUM_CHANNELS; i++ ) { static const char *names[] = { "left", "right" };//+static_assert re. NUM_CHANNELS.. try { outChannels[i].construct( env, names[i], false, false ); rl.log( " created channel " ); rl.log( i ); } catch( ... ) { Genode::error( "cannot create Audio_out #", i, " aka ", names[i] ); throw; } } // subscribe to mixer signals //outChannels[0]->progress_sigh( sigHandler ); // start streaming to mixer for( int i = 0; i < NUM_CHANNELS; i++) outChannels[i]->start(); rl.log( " ctor complete; " ); } hog::ConsumerNative::~ConsumerNative() { GeTRACE( "native_out.DTor" ); // unsubscribe //outChannels[0]->progress_sigh( Genode::Signal_context_capability() ); // stop streaming to mixer Halt(); } void hog::ConsumerNative::Halt() { for( int i = 0; i < NUM_CHANNELS; i++) { if( outChannels[i].constructed() ) { outChannels[i]->stop(); //outChannels[i].destruct(); } } } bool hog::ConsumerNative::IsAlmostFull() { // RETURNS: (comparison of queued() and <upper_queue_threshold>): // // - strictly superior: -> error ! (should not happen, although it does) // - equal: -> return true // - strictly inferior: -> return false (i.e. allow additional queueing up) ///FIXME-2: strange, I can't get this message to disappear, even if I enlarge buffer sizes and the queue threshold and decrease the snooze time... I get this situation at the beginning of each mp3, for a second or two, even after configuring to queue up 64 packets... File a ticket ? Or am I using the Audio_out session wrong ? ///FIXME-2: also, why can't I play two mp3s simultaneously? That's the very thing that the mixer ought to support :-P Yet starting a second mp3 in eg ArmyKnife yields choppy sound or no sound, even as the first mp3 in AC continues to play... What am I doing wrong ? auto count = outChannels[0]->stream()->queued(); if( count > upper_queue_threshold ) { rl.log( " *** Impossible Queue Count (overflow?): " ); rl.log( count ); rl.Dump();/// } return outChannels[0]->stream()->queued() >= upper_queue_threshold ; } ///++ Consume"Frames" void hog::ConsumerNative::ConsumeBuffer( Frame_data & frame_data ) { //const unsigned count_frames = frame_data.read_avail() / (NUM_CHANNELS*sizeof(float)); GeTRACE( " ConsumeBuffer: need to tx ", frame_data.read_avail(), " bytes (", count_frames, " frames)" ); if( frame_data.read_avail() <= 0 ) { rl.log( " *native sink: empty production, return* " ); //Genode::log( "** native sink: empty production? failed to decode?" ); // return; // } ///FIXME-2: how come queued() returns 1 (instead of 0) the very first time we're called, before we even start to queue buffers ? const auto queued = outChannels[0]->stream()->queued(); if( queued <= 3 ) { rl.log( "\n Low Queue Count (underrun ? or maybe we're just starting up ?): " ); rl.log( queued ); /********/ rl.Dump(); } //get rid of this?: { rl.log( "\n t- pipeline: " ); rl.log( queued ); rl.log( " packets; " ); rl.log( frame_data.read_avail() ); rl.log( " bytes in fifo; " ); if( IsAlmostFull() ) { rl.log( " BAIL OUT; " ); //rl.Dump(); //Genode::warning( "BAIL OUT, there's already ", queued, " packets queued" ); // return; // } } #if 0 float tmp[AUDIO_OUT_PACKET_SIZE / sizeof(float)] = { 0 }; // that's a sizeable buffer at 4096 bytes big, let's use a DS instead: #else float * tmp = writeBuf.local_addr<float>(); #endif // loop until we exhaust <frame_data> or until we reach the desired buffering level // while( frame_data.read_avail() >= AUDIO_OUT_PACKET_SIZE && false==IsAlmostFull() ) { size_t const n = frame_data.read( tmp, AUDIO_OUT_PACKET_SIZE ); rl.log( "\n pop " ); rl.log( n ); rl.log( " bytes; " ); if( n != AUDIO_OUT_PACKET_SIZE ) { rl.log( " ** n != AUDIO_OUT_PACKET_SIZE ** " ); Genode::warning( "retrieved fewer frames than expected" ); // break; // } packageFloats( tmp ); } //Genode::log( " sink.queued: ", outChannels[0]->stream()->queued() ); ///FIXME-2: what about the very last few bytes of an mp3 ? Need to send a zero-ed out, "partial" packet // and to read exactly what remains, like: // // frame_data.read( last_buf, frame_data.read_avail() ); } //++ "pushOneStereoPacketPair()" void hog::ConsumerNative::packageFloats( const float * const decoded_interleaved ) { // INPUT: // - float array, one left packet + one right packet's worth (two channels interleaved) // OUTPUT: // - left packet // - right packet Audio_out::Packet * left_pk = nullptr; try { rl.log(" alloc;"); left_pk = outChannels[0]->stream()->alloc(); } catch ( ... ) { Genode::error( "audio-out: RESET on Under-Run (?)" ); rl.log(" *RESET On Underrun* SHOULD WE TRY AGAIN ? (in a for() loop) ?\n"); // seems we're more stable(?) without this: //outChannels[0]->stream()->reset(); ///ToDo: or maybe, to the contrary, the above could reduce the ~3 seconds "blanks" to be much shorter ? Either up there, or in ConsumeBuffer() ? Well I no longer get audio silence/blanks with the queue threshold increased to 64... } Audio_out::Packet * right_pk = nullptr; { auto pos = outChannels[LEFT ]->stream()->packet_position( left_pk ); right_pk = outChannels[RIGHT]->stream()->get( pos ); } #if 0 // DEBUG: Square wave generator... // for( unsigned i = 0; i < Audio_out::PERIOD; i++ ) { float tmp_val = 0.; if( i < Audio_out::PERIOD/2.0 ) tmp_val = 0.25; else tmp_val = -0.25; /**/float * floats[NUM_CHANNELS] = { left_pk->content(), right_pk->content() }; floats[LEFT ][i] = tmp_val; floats[RIGHT][i] = tmp_val; } #else //fillFloats( left_pk->content() ); //fillFloats( right_pk->content() ); // Stereo sample loop (one pair of packets) // const unsigned frames_per_packet = Audio_out::PERIOD; for( unsigned f = 0; f < frames_per_packet; f++ ) { float * chan_pkt[NUM_CHANNELS] = { left_pk->content(), right_pk->content() }; chan_pkt[LEFT ][f] = decoded_interleaved[ f * NUM_CHANNELS +LEFT ]; chan_pkt[RIGHT][f] = decoded_interleaved[ f * NUM_CHANNELS +RIGHT ]; } #endif rl.log( " submit#" ); rl.log( outChannels[0]->stream()->packet_position((left_pk)) ); // push packets outChannels[LEFT ]->submit( left_pk ); outChannels[RIGHT]->submit( right_pk ); { auto queued = outChannels[0]->stream()->queued(); rl.log( "; t+ pipeline: " ); rl.log( queued ); rl.log( " pkts; " ); } } #if 0 void hog::ConsumerNative::handle_audio_out_progress() { Genode::log( "handle_audio_out_progress" ); /* if constructed.. auto queued = outChannels[0].stream()->queued(); log( queued ); */ } #endif |
Changes to genode-haiku/haiku-on-genode/kits/media/_ge_AudioOut_oss.cpp.
︙ | ︙ | |||
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | // libc.lib.so #include <fcntl.h> // ioctl #include <sys/soundcard.h> // ioctl codes: OSS_GETVERSION, SNDCTL_DSP_HALT, etc class hog::Consumer { public: Consumer( Genode::Env & env // Genode::Signal_handler<Audio_player::Main> & sigh//progress_dispatcher; // Genode::Signal_context_capability sigh ); ~Consumer(); // void start_timer(); // void stop_timer(); void Halt(); void ConsumeBuffer( Frame_data & frame_data ); private: // Constants //xxx or: sizeof(short) * NUM_CHANNELS * PERIOD * how_many_periods_to_batch_together //enum { BUFSIZE = 128 * 1024 }; private: // Data int fdOss; | > > > | | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | // libc.lib.so #include <fcntl.h> // ioctl #include <sys/soundcard.h> // ioctl codes: OSS_GETVERSION, SNDCTL_DSP_HALT, etc //++ "ConsumerOSS" class hog::Consumer { public: Consumer( Genode::Env & env // Genode::Signal_handler<Audio_player::Main> & sigh//progress_dispatcher; // Genode::Signal_context_capability sigh ); ~Consumer(); // void start_timer(); // void stop_timer(); void Halt(); void ConsumeBuffer( Frame_data & frame_data ); bool IsAlmostFull(); private: // Constants //xxx or: sizeof(short) * NUM_CHANNELS * PERIOD * how_many_periods_to_batch_together //enum { BUFSIZE = 128 * 1024 }; private: // Data int fdOss; ///ToDo: use a DS, instead of a (smaller/riskier) stack-allocated buf (careful, that class gets "clashed" by hai-libc, make this a separate compilation unit) #if 0 Attached_ram_dataspace writeBuf; #endif #if 0 ///ToDo: any way to get a signal, when OSS is ready to receive new buffers ? void _handle_timeout( Genode::Duration ); |
︙ | ︙ | |||
100 101 102 103 104 105 106 | #if 0 void hog::Consumer::start_timer() { //GeTRACE( "oss.start" ); | | | 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | #if 0 void hog::Consumer::start_timer() { //GeTRACE( "oss.start" ); // initiate timer (it will self-sustain afterwards with repeated calls to schedule()) Genode::Microseconds delay { 9000 }; _timeout.schedule( delay ); } void hog::Consumer::stop_timer() { //GeTRACE( "oss.stop" ); |
︙ | ︙ | |||
197 198 199 200 201 202 203 | { Genode::error( "oss-out: buffer overflow, bailing out" ); // return; // } #else | | | 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | { Genode::error( "oss-out: buffer overflow, bailing out" ); // return; // } #else ///ToDo: uh ? can this get "too low" (yes: for WAV files eg) or "too high" and overflow ? : #endif unsigned count_frames = frame_data.read_avail() / (NUM_CHANNELS*sizeof(float)); //Genode::log( " chunk size: ", count_frames, " frames" ); // typically: 1152 frames #if 0 short * buf = writeBuf.local_addr<short>(); ///ToDo: should use a ram_dataspace for tmp[] as well, not just buf[] |
︙ | ︙ | |||
239 240 241 242 243 244 245 | buf[i * NUM_CHANNELS +RIGHT] = tmp[i * NUM_CHANNELS + RIGHT] * 32700.; } } #endif int res = 0; | > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > < | < < < < > | < | < < < < < < > | < < > < > > > > > > | 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 | buf[i * NUM_CHANNELS +RIGHT] = tmp[i * NUM_CHANNELS + RIGHT] * 32700.; } } #endif int res = 0; Libc::with_libc([&] () { // This might hang when used against *black_hole* (e.g. in Qemu), if the write rate isn't sustained, // after a few seconds BH gives up and write() never returns ; seems rock solid against pci_audio_drv however. ///ToDo: it seems write() hangs on (bare metal) HDA too, not just with black_hole, in fact, // re-enable log() lines to confirm ? //UPD: could the "hang" actually be caused by a vfs server crash ? Nope, other apps (AK, Lightning etc) continue to work with vfs, even after AC locks-up. //UPD: seems the lock-ups are unrelated to audio/OSS, but to the scheduler thread instead ? // Heisen-Bug: T410 no longer reproduces the write() hang, after adding this log()s ? // UPD: yea it does, though now it takes 2+ hours // Genode::log( " ..write ", sizeof(buf), " bytes:" ); // GENODE_LOG_TSC_NAMED( 600, "oss.write" ); res = write( fdOss, buf, sizeof( buf ) ); //Genode::log( " ..done with write -> ", res ); }); if( res != signed(sizeof(buf)) ) Genode::warning( "OSS.write got ", res, " instead of ", sizeof(buf) ); GeTRACE( " consume done!" ); } bool hog::Consumer::IsAlmostFull() { int res = 0; //int oss_ver = 0; struct audio_errinfo err_info = {}; oss_count_t optr = {}; struct audio_buf_info o_inf = {}; Libc::with_libc([&] () { //res = ioctl( fdOss, OSS_GETVERSION, &oss_ver ); res = ioctl( fdOss, SNDCTL_DSP_GETERROR, &err_info ); res = ioctl( fdOss, SNDCTL_DSP_CURRENT_OPTR, &optr ); res = ioctl( fdOss, SNDCTL_DSP_GETOSPACE, &o_inf ); }); #if 0 //Genode::log("ioctl -> ", res, " ver ", oss_ver ); Genode::log("ioctl -> ", res, " play_underruns ", err_info.play_underruns ); Genode::log("ioctl -> ", res, " samples ", optr.samples, " fifo_samples ", optr.fifo_samples ); Genode::log("ioctl -> ", res, " fragments ", o_inf.fragments, " fragstotal ", o_inf.fragstotal, " fragsize ", o_inf.fragsize, " bytes ", o_inf.bytes ); #endif int free_perc = -1; if( o_inf.fragstotal > 0 ) { free_perc = 100 * o_inf.fragments; free_perc /= o_inf.fragstotal; } GeTRACE( " audio queue free space: ", free_perc, " %" ); // We draw the line for "almost full" at 10% free space (90% full) : beyond that, // we ask the caller to snooze for a bit before submitting more audio data.. // Might spare us the blockage that occurs when doing "blocking writes" that never un-block? // UPD: no change, the lock-ups still occur, but now they occur at 90% full instead :-) :-/ // return free_perc <= 10; } |
Changes to genode-haiku/haiku-on-genode/kits/media/_ge_AvDecoder.cpp.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // Genode back-end for: // BMediaTrack // Adapted (slightly) from original cnuke@genode ----------- // avcodec.so avformat.so avutil.so avresample.so extern "C" { # include <libavcodec/avcodec.h> # include <libavformat/avformat.h> # include <libavutil/dict.h> # include <libavutil/frame.h> # include <libavutil/opt.h> # include <libavresample/avresample.h> }; // ~extern "C" | > > > > > < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | // Genode back-end for: // BMediaTrack // libc.lib.so #include <libc/component.h> // with_libc // Adapted (slightly) from original cnuke@genode ----------- // avcodec.so avformat.so avutil.so avresample.so extern "C" { # include <libavcodec/avcodec.h> # include <libavformat/avformat.h> # include <libavutil/dict.h> # include <libavutil/frame.h> # include <libavutil/opt.h> # include <libavresample/avresample.h> }; // ~extern "C" class hog::FramesProducer { // Decodes mp3 ..etc audio files, extracting/producing audio frames // to be fed to FramesConsumer. public: // Xtors FramesProducer( Genode::String<MAXPATHLEN> trackpath ); ~FramesProducer(); |
︙ | ︙ | |||
402 403 404 405 406 407 408 409 410 411 412 413 414 415 | // break; // } if( _packet.stream_index != _stream->index ) { Genode::log( " unexpected stream packet -- video stream maybe??" ); // continue; } int finished = 0; //++ "got_full_frame" | > | 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 | // break; // } if( _packet.stream_index != _stream->index ) { ///later: I sometimes get this "unexpected stream packet" message: Genode::log( " unexpected stream packet -- video stream maybe??" ); // continue; } int finished = 0; //++ "got_full_frame" |
︙ | ︙ | |||
479 480 481 482 483 484 485 | //_d_ samplesDecoded += bytes / frame_size; samplesDecoded += _conv_frame->nb_samples;//xx uh if "nb_samples" is actually a nb_frames, we should *2 to get the nb_samples..?? ; written += decodedFrames.write( data, bytes ); | | > > | 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 | //_d_ samplesDecoded += bytes / frame_size; samplesDecoded += _conv_frame->nb_samples;//xx uh if "nb_samples" is actually a nb_frames, we should *2 to get the nb_samples..?? ; written += decodedFrames.write( data, bytes ); ; if( written != bytes ) Genode::warning( "some decoded frames were discarded/lost in the ether, we're decoding faster than Audio-out consumes ?!" ); GeTRACE(" producer: frame_date.write(", bytes, ") returns ", written); // This trace'ing exhibits the problem (also in e.g. audio_player) when playing 22050 Hz etc mp3s: // ! <nb_samples> is zero after a while ! // Genode::log( " _conv_frame->linesize = ", bytes, " t+ samplesDecoded = ", samplesDecoded, " nb_samples: ", _conv_frame->nb_samples); in = nullptr; // ! |
︙ | ︙ |
Added genode-haiku/haiku-on-genode/kits/media/_ge_consumer_base.h.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | /* * Copyright 2023, ttcoder * All rights reserved. Distributed under the terms of the MIT license. */ #ifndef _ge_consumer_base_H #define _ge_consumer_base_H // base.lib.a #include <audio_out_session/connection.h> // libmedia.so #include "_RingLog.h" /**************/ //#define GeTRACE Genode::log #define GeTRACE(x...) ; namespace hog { // constants // enum { LEFT, RIGHT, NUM_CHANNELS }; enum { AUDIO_OUT_PACKET_SIZE = Audio_out::PERIOD * NUM_CHANNELS * sizeof(float) }; // i.e. "audio_pkt_size = frames_per_packet * samples_per_frame * bytes_per_sample" enum { snooze_micros = 25000/*55000*/ }; // ca. 40 Hz // pause for that many µs between each "wake-up -> decode -> fill queue" step. enum { upper_queue_threshold = 64 /*32*/ /*16*/ }; // upper limit of how many mixer/audio_drv-side packets to fill before snoozing. // max 254 (for ca. 3 seconds worth of audio buffering). // min circa 16 (otherwise it's hard to avoid under-runs, i.e. hard to be certain to keep up and queue packets in time) enum { writeavail_threshold = 16384 }; // keep filling the frames ring until there's only that many free bytes left // (we don't try to fill to the brim as the AV decoder deals in "chunks", and if the last chunk does not fit in full it's too late to "rewind" the decoder) // (otherwise we get errors like: << attempting to write 9216 bytes but only 5120 free >> enum { ringbuf_size_kb = 256 }; // 256 KB -> enough to package <upper_queue_threshold> packets (64 packets) // types // class FramesProducer; class ConsumerBase; class ConsumerNative; template <size_t> class SimpleRingBuffer; typedef SimpleRingBuffer< ringbuf_size_kb/*256*/ * 1024 > Frame_data; /****** Latency config ****** To increase performance/reliability: - increase queue threshold (higher latency) - decrease snooze (faster responsivness by more aggressive polling) To stress-test: - decrease queue threshold (lower latency) - increase snooze Changing the size of the decoded-frames FIFO plays a lesser role than the other settings. Latency tests (on T410, with snooze(119000)): - 24 packets deep: we frequently "miss a beat", with a "blank" of ca. 2 second (why so long ? full 255 packets cycle maybe ?) - 32 packets deep: same as 24 deep - 64 packets deep: looks good, no need to go up to 128 it seems The "blanks" get logged as "Low Queue Count"... Bug in queued() implementation ? Maybe when empty it tends to not return "0" but rather "255" ? Let's add a "Impossible Queue Count" logger. */ }; template <size_t CAPACITY> class hog::SimpleRingBuffer { // AKA "trivial ring buffer" AKA "not really a *ring* buffer at all, using memmove() instead" public: SimpleRingBuffer() : currentSize( 0 ) { } size_t write_avail() const { return CAPACITY - currentSize; } size_t write( const void * src, size_t len ) { if( len > write_avail() ) { ///ToDo: instead of rejecting new frames I should discard some oldest frames, just enough to make room for newest frames, so as to minimize the damage... Genode::warning( "frames overflow -- ringbuffer: attempting to write ", len, " bytes but only ", write_avail(), " free" ); // return 0; // } // T0 memcpy( bufData + currentSize, src, len ); // T+ currentSize += len; return len; } size_t read( void * dst, size_t len ) { if( len > currentSize ) { Genode::error( "ringbuffer: attempting to read ", len, " bytes, but only ", currentSize, " available" ); // return 0; // } // T- memcpy( dst, bufData, len ); // T0 memmove( bufData, bufData+len, currentSize-len ); currentSize -= len; // T+ memset( bufData+currentSize, '\0', len ); return len; } size_t read_avail() const { return currentSize; } private: // Data uint8 bufData[ CAPACITY ]; size_t currentSize; }; class hog::ConsumerBase { public: ConsumerBase( Genode::Env & env ) : rl( env, false/**/, false ) { } virtual ~ConsumerBase() { } //+ "IsBelowThreshold()"... virtual bool IsAlmostFull() = 0; virtual void ConsumeBuffer( Frame_data & frame_data ) = 0; virtual void Halt() = 0; public: // Data (!) RingLog rl; }; #endif // _ge_consumer_base_H |