Reading a file located in memory with libavformat - c++

I'm currently trying to read small video files sent from a server
In order to read a file using libavformat, you are supposed to call
av_open_input_file(&avFormatContext, "C:\\path\\to\\video.avi", 0, 0, 0);
The problem is that in this case the file is not on the disk, but in memory.
What I'm doing for the moment is downloading the file, writing it on the disk using a temporary name, and then calling av_open_input_file with the temporary file name, which is not a very clean solution.
In fact what I want is a function like av_open_custom(&avFormatContext, &myReadFunction, &mySeekFunction); but I didn't find any in the documentation.
I guess it is technically possible, since the name of the file is not something that helps the library determine which format it is using.
So is there a function like this, or an alternative to av_open_input_file?

It's funny how I always find the solution by myself right after I post the question on this site, even though I've been working on this problem for hours.
In fact you have to initialize avFormatContext->pb before calling av_open_input, and pass to it a fake filename.
This is not written in the documentation but in a commentary directly in the library's source code.
Example code if you want to load from an istream (untested, just so somebody which has the same problem can get the 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);

This is great information and helped me out quite a bit, but there are a couple of issues people should be aware of. libavformat can and will mess with your buffer that you gave to avio_alloc_context. This leads to really annoying double-free errors or possibly memory leaks. When I started searching for the problem, I found https://lists.ffmpeg.org/pipermail/libav-user/2012-December/003257.html which nailed it perfectly.
My workaround when cleaning up from this work is to just go ahead and call
av_free(avioContext->buffer)
and then setting your own buffer pointer (that you allocated for your avio_alloc_context call) to NULL if you care.

Tomaka17's excellent answer gave me a good start toward solving an analogous problem using Qt QIODevice rather than std::istream. I found I needed to blend aspects of Tomaka17's solution, with aspects of the related experience at http://cdry.wordpress.com/2009/09/09/using-custom-io-callbacks-with-ffmpeg/
My custom Read function looks like this:
int readFunction(void* opaque, uint8_t* buf, int buf_size)
{
QIODevice* stream = (QIODevice*)opaque;
int numBytes = stream->read((char*)buf, buf_size);
return numBytes;
}
...but I also needed to create a custom Seek function:
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();
}
...and I tied it together like this:
...
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);
...
Note I have not yet worked out the memory management issues.

Related

avformat_write_header() doesn't work when writing data to memory instead of file

i want to resample a given input format from memory to memory everything is good so far.
but when trying to get the output header from ffmpeg it doesn't work.
here i allocate the context and pass the write_buffer function pointer so that it doesn't write to a file but instead it will call my function with the required data
unsigned char * aviobuffer = (unsigned char *) av_malloc (32768);
AVIOContext * avio = avio_alloc_context (aviobuffer, 32768,1, NULL, NULL, write_buffer, NULL);
AVFormatContext* containerContext;
avformat_alloc_output_context2(&containerContext, NULL, "s16le", NULL);
containerContext->pb = avio;
here is my write_buffer function
std::vector<char>* data;
int write_buffer(void *opaque, uint8_t *buf, int buf_size)
{
std::vector<char> tmp;
tmp.assign(buf, buf + buf_size);
data->insert(data->end(), tmp.begin(), tmp.end());
return buf_size;
}
now when i call avformat_write_header() it doesn't call my write_buffer() function + it returns 0 which means success.
int ret = avformat_write_header(containerContext, NULL);
after that i call the appropriate functions to get the data body itself and my write_buffer() get called normally so i am now left with the data body with no header !!
how can i get the output header anyways?
well, after a lot of debugging that led me to discover the way ffmpeg writes format headers.
long story short, some formats are associated with special functions for writing their headers.
the "s16le" format inside ffmpeg is not associated with one. but surprisingly as i stated in the question ffmpeg can write its data body but no header!!
so i searched for a format that is close to what i want and supports writing its header. i found the "wav" format, tried it and it worked nicely. fortunately wav's default is s16le which is exactly what i want.
so in conclusion i changed this line of code
avformat_alloc_output_context2(&containerContext, NULL, "s16le", NULL);
to
avformat_alloc_output_context2(&containerContext, NULL, "wav", NULL);

Double pointer out array parameter exception

I have the following function which I intend to load shaders with (error checking removed for brevity):
unsigned int readFile(const char* file, char** buffer)
{
FILE* fp;
fopen_s(&fp, file, "rb");
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
fseek(fp, 0, SEEK_SET);
*buffer = new char[size + 1];
fread(*buffer, 1, size, fp);
*buffer[size] = 0; // BAD LINE, only [0] is fine.
fclose(fp);
return 0;
}
It is called with:
char* fileContents = nullptr;
readAllFile("test.txt", &fileContents);
I cannot figure out how to fix the bad line. When I use char*& buffer as the out parameter it works fine, and a reference in large part is functionally the same as a pointer right?
The error is:
Exception thrown at 0x011919D4 in My World_Win32_Debug.exe:
0xC0000005: Access violation writing location 0xCCCCCCCC.
How should I set the last element of the buffer to 0 (null terminator)? I've looked through the debugger and the contents of buffer are valid, and set properly until reaching the bad line despite buffer being referenced the same way every time.
With only [0] working fine, that indicates to me I'm address only the pointer itself, not it's data, but I don't know how to address it otherwise. Every other way I've tried gives a compile error.
I'm aware that references are preferred in many cases, and there's other problems here, but I do need to know why I have the problem above first.
You want this:
(*buffer)[1] = 0;
instead of:
*buffer[1] = 0; // same as *(buffer[1]) = 0;
Out of desperation, I tried putting stars and brackets everywhere and I realized the problem. Order of operations is attempting to deference buffer[size] not buffer. Using (*buffer)[size] fixes the problem.

strange fopen() behavior; giving unexpected NULL in case A and works perfect in similar case B

I am baffled with the way fopen() is responding to my code. Note that I am using it to write binary files read from FPGA. The scenario is this
When I WRITE data to FPGA based system and then read back (after processing), only 307 files in binary can be saved, after which the fopen() returns NULL value
When I only read from FPGA (no writing at all) then it saves perfectly in all the 500 files.
When I do both cases for writing in .txt files, every thing goes perfect
CASE 1 is my problem and the real-world implementation I need. Hence I cannot understand this strange behaviour. One thing is assured with several testing that the whole problem is with the PC/C++ side and FPGA has nothing to do with it.
The first code is the calling of function, the second code is the function for writing binary files itself
...
sprintf(fwname, "data/uwpi%d.bin", fnum++);
opfile = fopen(fwname , "w");
rc = sipif_readdata(pInDMA, (40000)*2);
save_binary(pInDMA, 40000, fwname, opfile , fnum);
...
The following is the function details
static ULONG save_binary(void *buf, int bufsize, const char *filename, FILE *fOutFile, int fnum){
// cast our stamp less pointer to a short
short *buf16 = (short *)buf;
fOutFile = fopen(filename, "ab");
if(fOutFile==NULL) {
//comment added to verify my claim
printf("Null value found at file %d\n", fnum);
return -3; }
printf("fnum is %d\n", fnum);
fwrite(buf, 2, bufsize, fOutFile);
fclose(fOutFile);
return 0;
}

Sending Files With Sockets

I'm trying to create a basic HTTP server to learn more about how it works. I'm having difficulties with sending binary files to the client. My code is as below:
char * buffer = (char *)malloc(sizeof(char) * 512);
fseek(content_file, 0, SEEK_SET);
while (!feof(content_file)) {
size_t read = fread(buffer, sizeof(char), sizeof(buffer), content_file);
if (read > 0) {
client->send((const void *)buffer);
}
}
fclose(content_file);
free(buffer);
Now I know it can send some unnecessary data after the last block read but before trying to fix it, I want to know what's wrong with it. It was working fine for text files and I was using fgets. But after switching to fread to support binary files, text files are corrupted and became something like this: ThisÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ ("This" is the only correct part in the sent data)
Obviously I'm missing something but can you please help me to do this correctly?
Edit:
Using a buffer_size value instead of sizeof(buffer) fixed the missing/corrupted data problem.
You problem is that sizeof(buffer) gives you the size of the pointer, not what it points to.
Add a buffer_size and use that both for malloc and freed.

C++ gsoap mime/dime for binary files in windows

I'm pretty close to losing my head here ;)
I'm developing a service that uses gsoap. I would like to return a mime response.
I have everything working, but when reading binary files, all kind of files like jpeg, pdf, etc... contains the \0 char several times over the data (if opened with notepad can see a lot of NUL).
So any code for reading a raw file fails miserably once it finds the end-of-file char. I have tried to replace the \0 but the file becomes incorrect to display.
I have also tried several methods including the example that comes with gsoap.
So resuming,
fstream generic code doesn't work.
for (i = 0; i < MAX_FILE_SIZE; i++)
{ if ((c = fgetc(fd)) == EOF)
break;
image.__ptr[i] = c;
}
doesn't work also
QFile::ReadAll works but when converting QString to char* the array is trimmed in the first NUL.
So, which is the best aproach to read an entire binary file? Its crazy how sometimes C++ at the basic.
Thanks in advance.
I have tried this as retnick suggested below
UrlToPdf urlToPdf;
urlToPdf.getUrl(&input, &result);
QByteArray raw = urlToPdf.getPdf(QString(result.data.c_str()));
int size = raw.toBase64().size();
char* arraydata = new char[size];
strcpy(arraydata, raw.toBase64().data());
soap_set_mime(this, "MIME_boundary", NULL);
if(soap_set_mime_attachment(this, arraydata, size, SOAP_MIME_BASE64, "application/pdf", NULL, NULL, NULL))
{
soap_clr_mime(this);
soapMessage = this->error;
}
but no luck... the mime response is bigger than the actual file...
David G Ortega
to read binary files use fread()
Once you read it treat it as an array of bytes not as a string. No string functions allowed.
EDIT: The gSOAP documentation section 14.1 explains how to send MIME attachments. I only refer to the relevant function (please read it all).
int soap_set_mime_attachment(struct soap *soap, char *buf_ptr, size_t buf_size,
enum soap_mime_encoding encoding,
const char *type, const char *id,
const char *location, const char *description);
char *buf_ptr is your buffer.
size_t buf_size is the length of your buffer.
So just do your QFile::ReadAll.
this gives you back a QByteArray. The QByteArray has the method
QByteArray QByteArray::toBase64 () const
this will return a
QByteArray base64image = QByteArray::toBase64(rawImage);
so now just do
soap_set_mime(soap, "MIME_boundary", "<boundary.xml#just-testing.com>");
/* add a base64 encoded image (base64image points to base64 data) */
soap_set_mime_attachment(soap,
base64image.data(), base64image.size(),
SOAP_MIME_BASE64, "image/jpeg",
"<boundary.jpeg#just-testing.com>", NULL, NULL);
I have not tested this but should be close to finished.
QFile::ReadAll works but when converting QString to char* the array is trimmed in the first NUL.
Are you sure it's actually trimmed or you just can't print/view the array in the debugger [since C-style strings are 0 terminated]?
If the QString itself is not enough for your needs you may want to convert it to a std::vector or similar using the range constructor or range assign, you'll have lots less grief towards the how much data the container holds.
EDIT:
Here's some sample code for fstream reading from a binary file:
std::ifstream image( <image_file_name>, std::ios_base::in | std::ios_base::binary );
std::istream_iterator< char > image_begin( image ), image_end;
std::vector< char > vctImage( image_begin, image_end );
The std::ios_base::binary is the most important part of the thing (similar to fopen/fread ["rb"] & probably QFile has something similar)
Also posting some sample code usually helps in getting the right answer.
HIH
I have the solution for this... As renick suggested I tried his idea but it failed without undestanding it so much... From a logical point of view recnick was right... bat the truth is that any king of string manipulation using QT QByteArray, std or mem is going to stop when findind the first \0 char, Qt QString can do it without problems but when converting it to c string (char*) the data will be again trimmed with the first \0
I found that using QDataStream::readRawData reads the file into a char* given the size to read. So thats how I accomplished the deal...
QFile file("test.pdf");
file.open(QIODevice::ReadOnly);
int size = file.size();
char* buffer = new char[size];
QDataStream stream(&file);
stream.readRawData(buffer, size);
soap_set_mime(this, "MIME_boundary", NULL);
if(soap_set_mime_attachment(this, buffer, size, SOAP_MIME_BINARY, "application/pdf", NULL, NULL, NULL))
{
soap_clr_mime(this);
soapMessage = this->error;
}
Note that in the line
if(soap_set_mime_attachment(this, buffer, size, SOAP_MIME_BINARY, "application/pdf", NULL, NULL, NULL))
I'm still using the size var instead of doing sizeof(buffer) or any other aproach since this one is going to trimm again the data qhen finding the first \0...
Hope this helps...
David G Ortega