从GStreamer Appsink保存MJPEG图像的最优方案及多管道实现崩溃问题解决
Great question! Since you're working with an MJPEG stream, each frame in your GstSample is already a fully formed JPEG image—you don't need to spin up a new GStreamer pipeline or do any encoding/decoding at all. The root cause of your crashes is the messy pipeline creation/teardown logic (especially those unreliable usleep calls and incorrect EOS handling), and we can eliminate all that by writing the raw JPEG data directly to disk.
The Simplest Solution: Write the Buffer Data Directly to File
Here's how to rewrite your saveSampleFromAppsinkJpeg function to skip the pipeline entirely. We'll extract the raw JPEG bytes from the GstBuffer and write them straight to a file:
int saveSampleFromAppsinkJpeg(GstSample *sample) { if (!sample) { std::cerr << "saveSampleFromAppsinkJpeg: Null sample received" << std::endl; return -1; } GstBuffer *buffer = gst_sample_get_buffer(sample); if (!buffer) { std::cerr << "saveSampleFromAppsinkJpeg: Null buffer in sample" << std::endl; return -1; } // Get the memory block from the buffer GstMemory *memory = gst_buffer_get_memory(buffer, 0); if (!memory) { std::cerr << "saveSampleFromAppsinkJpeg: Failed to get memory from buffer" << std::endl; return -1; } GstMapInfo info; // Map the memory to access raw bytes if (!gst_memory_map(memory, &info, GST_MAP_READ)) { std::cerr << "saveSampleFromAppsinkJpeg: Failed to map memory" << std::endl; gst_memory_unref(memory); return -1; } // Generate a unique filename to avoid overwriting (adjust path as needed) static int image_counter = 0; char filename[256]; snprintf(filename, sizeof(filename), "some/path_%d.jpg", image_counter++); // Write the raw JPEG data to file std::ofstream outfile(filename, std::ios::binary); if (!outfile.is_open()) { std::cerr << "saveSampleFromAppsinkJpeg: Failed to open file " << filename << std::endl; gst_memory_unmap(memory, &info); gst_memory_unref(memory); return -1; } outfile.write(reinterpret_cast<const char*>(info.data), info.size); outfile.close(); // Clean up resources gst_memory_unmap(memory, &info); gst_memory_unref(memory); gst_sample_unref(sample); std::cout << "Saved image to " << filename << std::endl; return 1; }
Why This Works
MJPEG streams consist of sequential JPEG images wrapped in a lightweight container. Each GstBuffer in your stream holds exactly one complete JPEG file's worth of data—no extra headers or encoding needed. By mapping the buffer's memory and writing those bytes directly, you're essentially copying the JPEG frame as-is to disk, with zero overhead from re-encoding or pipeline management.
What Was Wrong With Your Original Approach
Your original code creates a new GStreamer pipeline every time you save an image, which is inefficient and error-prone:
usleepcalls are unreliable—you can't guarantee the pipeline has finished processing the sample before sending EOS or tearing it down.- Incorrect EOS handling: You're manually posting EOS messages and calling
end_of_streammultiple times, which can cause state inconsistencies. - Resource leaks: If the pipeline doesn't reach
GST_STATE_NULLproperly, you leave GStreamer resources hanging, which eventually causes crashes after a few saves.
If You Must Use a Pipeline (For Future Expansion)
If you need to add processing later (like resizing or metadata injection), you should reuse a single pipeline instead of creating a new one each time. Here's a quick outline:
- Create the pipeline once at app startup (appsrc → multifilesink, plus any processing elements you need).
- When saving an image, update the multifilesink's
locationproperty and push the sample to the existing appsrc. - Use GStreamer's bus to wait for EOS or completion messages instead of
usleep—this ensures the pipeline finishes writing before you proceed. - Reset the pipeline's state (or just the sink's filename) for the next image.
But for your current use case, direct file writing is by far the best option—it's faster, simpler, and eliminates the crash risk entirely.
内容的提问来源于stack exchange,提问作者kochy

