2012-03-07 19 views
24

Attualmente sto cercando di leggere i file video di piccole dimensioni inviati da un serverLa lettura di un file che si trova nella memoria con libavformat

Per leggere un file utilizzando libavformat, si suppone di chiamare

av_open_input_file(&avFormatContext, "C:\\path\\to\\video.avi", 0, 0, 0); 

Il problema è che in questo caso il file non è sul disco, ma in memoria.

Quello che sto facendo per il momento sta scaricando il file, la scrittura sul disco utilizzando un nome temporaneo, e quindi chiamando av_open_input_file con il nome del file temporaneo, che non è una soluzione molto pulita.

In realtà quello che voglio è una funzione come av_open_custom(&avFormatContext, &myReadFunction, &mySeekFunction); ma non ho trovato alcun nella documentazione. Immagino che sia tecnicamente possibile, dal momento che il nome del file non è qualcosa che aiuta la libreria a determinare quale formato sta usando.

Quindi esiste una funzione come questa o un'alternativa a av_open_input_file?

+0

Sfuggi ai tuoi! ;-) – Konrad

risposta

30

È divertente come trovo la soluzione da solo subito dopo aver postato la domanda su questo sito, anche se ho lavorato su questo problema per ore.

In effetti, è necessario inizializzare avFormatContext->pb prima di chiamare av_open_input e passare ad esso un nome file falso. Questo non è scritto nella documentazione ma in un commento direttamente nel codice sorgente della biblioteca.

codice di esempio, se si desidera caricare da un istream (non testato, solo così qualcuno che ha lo stesso problema può ottenere l'idea)

static int readFunction(void* opaque, uint8_t* buf, int buf_size) { 
    auto& me = *reinterpret_cast<std::istream*>(opaque); 
    me.read(reinterpret_cast<char*>(buf), buf_size); 
    return me.gcount(); 
} 

std::ifstream stream("file.avi", std::ios::binary); 

const std::shared_ptr<unsigned char> buffer(reinterpret_cast<unsigned char*>(av_malloc(8192)), &av_free); 
const std::shared_ptr<AVIOContext> avioContext(avio_alloc_context(buffer.get(), 8192, 0, reinterpret_cast<void*>(static_cast<std::istream*>(&stream)), &readFunction, nullptr, nullptr), &av_free); 

const auto avFormat = std::shared_ptr<AVFormatContext>(avformat_alloc_context(), &avformat_free_context); 
auto avFormatPtr = avFormat.get(); 
avFormat->pb = avioContext.get(); 
avformat_open_input(&avFormatPtr, "dummyFilename", nullptr, nullptr); 
+0

questo è bello sapere. qualcuno ha provato la soluzione senza usare la libreria std? – tom

+0

per gcc, 'me._stream.read' e' me._stream.gcount' dovrebbero essere solo me.read e me.gcount, vedere http://www.cplusplus.com/reference/iostream/istream/ – tmatth

+0

Questa soluzione funziona alla grande fino a quando ho bisogno di fare ricerca. Qualche idea su come far funzionare questa soluzione con la ricerca? Attualmente sto utilizzando avformat_seek_file con il contesto di formato sul flusso video e lo streaming audio separatamente. Quando si utilizza la ricerca su un file di streaming (url) funziona alla grande. Quando su un mp4 locale con questo metodo, ottengo '[mov, mp4, m4a, 3gp, 3g2, mj2 @ 00ee8360] stream 0, offset 0xfd97fc: file parziale'. – leetNightshade

7

risposta eccellente del Tomaka17 mi ha dato un buon inizio verso la soluzione di un problema analogo usando Qt QIODevice piuttosto che std :: istream. Ho trovato avevo bisogno di fondere gli aspetti della soluzione Tomaka17, con aspetti della relativa esperienza a http://cdry.wordpress.com/2009/09/09/using-custom-io-callbacks-with-ffmpeg/

mia abitudine funzione Read assomiglia a questo:

int readFunction(void* opaque, uint8_t* buf, int buf_size) 
{ 
    QIODevice* stream = (QIODevice*)opaque; 
    int numBytes = stream->read((char*)buf, buf_size); 
    return numBytes; 
} 

... ma ho anche bisogno di creare un custom Seek funzione:

int64_t seekFunction(void* opaque, int64_t offset, int whence) 
{ 
    if (whence == AVSEEK_SIZE) 
     return -1; // I don't know "size of my handle in bytes" 
    QIODevice* stream = (QIODevice*)opaque; 
    if (stream->isSequential()) 
     return -1; // cannot seek a sequential stream 
    if (! stream->seek(offset)) 
     return -1; 
    return stream->pos(); 
} 

... ed ho legato insieme in questo modo:

... 
const int ioBufferSize = 32768; 
unsigned char * ioBuffer = (unsigned char *)av_malloc(ioBufferSize + FF_INPUT_BUFFER_PADDING_SIZE); // can get av_free()ed by libav 
AVIOContext * avioContext = avio_alloc_context(ioBuffer, ioBufferSize, 0, (void*)(&fileStream), &readFunction, NULL, &seekFunction); 
AVFormatContext * container = avformat_alloc_context(); 
container->pb = avioContext; 
avformat_open_input(&container, "dummyFileName", NULL, NULL); 
... 

Nota Non ho ancora risolto i problemi di gestione della memoria.

+0

'FF_INPUT_BUFFER_PADDING_SIZE' è stato modificato in' AV_INPUT_BUFFER_PADDING_SIZE' nelle ultime versioni ffmpeg –

9

Questa è un'ottima informazione e mi ha aiutato un po ', ma ci sono un paio di problemi che le persone dovrebbero essere a conoscenza. libavformat può e pasticcia con il buffer che hai dato a avio_alloc_context. Questo porta a fastidiosi errori double-free o forse a perdite di memoria. Quando ho iniziato a cercare il problema, ho trovato https://lists.ffmpeg.org/pipermail/libav-user/2012-December/003257.html che lo ha inchiodato perfettamente.

mia soluzione quando si pulisce da questo lavoro è quello di andare avanti e chiamare

av_free(avioContext->buffer) 

e quindi impostando il proprio puntatore del buffer (che è assegnato per la chiamata avio_alloc_context) NULL se si cura.

+0

Grazie. Farlo prima che av_free (avioContext) abbia risolto il mio problema di perdita di memoria! – Michel