I want to decode an audio file and store the PCM/int values into an array. For that I use gstreamer and the giostreamsink, which gives me a GMemoryOutputStream. So far so good, but how can I now access or loop through the GMemoryOutputStream?
What I did to get the gpointer:
gpointer out_data = g_memory_output_stream_get_data(G_MEMORY_OUTPUT_STREAM(stream));
but what can I do now with that gpointer? How can I access the stream data?
The full code that I have so far:
#include <string>
#include <stdio.h>
#include <gst/gst.h>
#include <gio/gio.h>
#include <boost/thread.hpp>
static void on_pad_added(GstElement *decodebin,
GstPad *pad,
gpointer data) {
GstElement *convert = (GstElement *) data;
GstCaps *caps;
GstStructure *str;
GstPad *audiopad;
audiopad = gst_element_get_static_pad(convert, "sink");
if (GST_PAD_IS_LINKED(audiopad)) {
g_object_unref(audiopad);
return;
}
caps = gst_pad_get_caps(pad);
str = gst_caps_get_structure(caps, 0);
printf("here %s\n",gst_structure_get_name(str));
if (!g_strrstr(gst_structure_get_name(str), "audio")) {
gst_caps_unref(caps);
gst_object_unref(audiopad);
return;
}
gst_caps_unref(caps);
gst_pad_link(pad, audiopad);
g_object_unref(audiopad);
}
static gboolean bus_call(GstBus *bus,
GstMessage *msg,
gpointer data) {
GMainLoop *loop = (GMainLoop*)data;
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS:
g_print ("End of stream\n");
g_main_loop_quit(loop);
break;
case GST_MESSAGE_ERROR: {
gchar *debug;
GError *error;
gst_message_parse_error(msg, &error, &debug);
g_free (debug);
g_printerr("Error: %s\n", error->message);
g_error_free(error);
g_main_loop_quit(loop);
break;
}
default:
break;
}
return true;
}
int main (int argc, char **argv) {
gst_init(&argc, &argv);
GstElement *pipeline, *source, *decode, *sink, *convert;
int rate = 44100;
int channels = 1;
int depth = 16;
bool output_signed = true;
GMainLoop *loop;
GstBus *bus;
guint bus_watch_id;
GMemoryOutputStream *stream;
gpointer out_data;
// loop
loop = g_main_loop_new(NULL, false);
// pipeline
pipeline = gst_pipeline_new("test_pipeline");
// sink
stream = G_MEMORY_OUTPUT_STREAM(g_memory_output_stream_new(NULL, 0, (GReallocFunc)g_realloc, (GDestroyNotify)g_free));
sink = gst_element_factory_make ("giostreamsink", "sink");
g_object_set(G_OBJECT(sink), "stream", stream, NULL);
// source
source = gst_element_factory_make("filesrc", "source");
g_object_set(G_OBJECT(source), "location", "/home/sam/Desktop/audio/audio.wav", NULL);
// convert
convert = gst_element_factory_make("audioconvert", "convert");
// decode
decode = gst_element_factory_make("decodebin", "decoder");
// link decode to convert
g_signal_connect(decode, "pad-added", G_CALLBACK(on_pad_added), convert);
// bus
bus = gst_pipeline_get_bus(GST_PIPELINE (pipeline));
bus_watch_id = gst_bus_add_watch(bus, bus_call, loop);
gst_object_unref(bus);
// add elements into pipeline
gst_bin_add_many(GST_BIN(pipeline), source, decode, convert, sink, NULL);
// link source to decode
gst_element_link(source, decode);
// caps
GstCaps *caps;
caps = gst_caps_new_simple("audio/x-raw-int",
"rate", G_TYPE_INT, rate,
"channels", G_TYPE_INT, channels,
"width", G_TYPE_INT, depth,
"depth", G_TYPE_INT, depth,
"signed", G_TYPE_BOOLEAN, output_signed,
NULL);
// link convert to sink
gst_element_link_filtered(convert, sink, caps);
gst_caps_unref(caps);
// start playing
gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING);
// iterate
g_print("Running...\n");
g_main_loop_run(loop);
// out of the main loop, clean up nicely
g_print("Returned, stopping playback\n");
gst_element_set_state(pipeline, GST_STATE_NULL);
g_print("Deleting pipeline\n");
gst_object_unref(GST_OBJECT(pipeline));
g_source_remove (bus_watch_id);
g_main_loop_unref(loop);
// get data
g_print("get data\n");
out_data = g_memory_output_stream_get_data(G_MEMORY_OUTPUT_STREAM(stream));
unsigned long size = g_memory_output_stream_get_size(G_MEMORY_OUTPUT_STREAM(stream));
unsigned long sizeData = g_memory_output_stream_get_data_size(G_MEMORY_OUTPUT_STREAM(stream));
std::cout << "stream size: " << size << std::endl;
std::cout << "stream data size: " << sizeData << std::endl;
for (int i = 0; i < 5; ++i) {
// std::cout << out_data[i] << std::endl; // not working
}
return 0;
}
I solved the problem, I had to cast the gpointer to gint16*:
std::vector<int16_t> data;
for (unsigned long i = 0; i < sizeData/2; ++i) {
data.push_back(((gint16*)out_data)[i]);
}
The casting has to be changed, depending on the depth you use e.g. depth 8 and unsigned guint8* and change also the for loop limit.
The full code for those interested:
#include <string>
#include <stdio.h>
#include <gst/gst.h>
#include <gio/gio.h>
#include <boost/thread.hpp>
static void on_pad_added(GstElement *decodebin,
GstPad *pad,
gpointer data) {
GstElement *convert = (GstElement *) data;
GstCaps *caps;
GstStructure *str;
GstPad *audiopad;
audiopad = gst_element_get_static_pad(convert, "sink");
if (GST_PAD_IS_LINKED(audiopad)) {
g_object_unref(audiopad);
return;
}
caps = gst_pad_get_caps(pad);
str = gst_caps_get_structure(caps, 0);
printf("here %s\n",gst_structure_get_name(str));
if (!g_strrstr(gst_structure_get_name(str), "audio")) {
gst_caps_unref(caps);
gst_object_unref(audiopad);
return;
}
gst_caps_unref(caps);
gst_pad_link(pad, audiopad);
g_object_unref(audiopad);
}
static gboolean bus_call(GstBus *bus,
GstMessage *msg,
gpointer data) {
GMainLoop *loop = (GMainLoop*)data;
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS:
g_print ("End of stream\n");
g_main_loop_quit(loop);
break;
case GST_MESSAGE_ERROR: {
gchar *debug;
GError *error;
gst_message_parse_error(msg, &error, &debug);
g_free (debug);
g_printerr("Error: %s\n", error->message);
g_error_free(error);
g_main_loop_quit(loop);
break;
}
default:
break;
}
return true;
}
int main (int argc, char **argv) {
gst_init(&argc, &argv);
GstElement *pipeline, *source, *decode, *sink, *convert;
int rate = 44100;
int channels = 1;
int depth = 16;
bool output_signed = true;
GMainLoop *loop;
GstBus *bus;
guint bus_watch_id;
GMemoryOutputStream *stream;
gpointer out_data;
// loop
loop = g_main_loop_new(NULL, false);
// pipeline
pipeline = gst_pipeline_new("test_pipeline");
// sink
stream = G_MEMORY_OUTPUT_STREAM(g_memory_output_stream_new(NULL, 0, (GReallocFunc)g_realloc, (GDestroyNotify)g_free));
sink = gst_element_factory_make ("giostreamsink", "sink");
g_object_set(G_OBJECT(sink), "stream", stream, NULL);
// source
source = gst_element_factory_make("filesrc", "source");
g_object_set(G_OBJECT(source), "location", "/home/sam/Desktop/audio/audio.wav", NULL);
// convert
convert = gst_element_factory_make("audioconvert", "convert");
// decode
decode = gst_element_factory_make("decodebin", "decoder");
// link decode to convert
g_signal_connect(decode, "pad-added", G_CALLBACK(on_pad_added), convert);
// bus
bus = gst_pipeline_get_bus(GST_PIPELINE (pipeline));
bus_watch_id = gst_bus_add_watch(bus, bus_call, loop);
gst_object_unref(bus);
// add elements into pipeline
gst_bin_add_many(GST_BIN(pipeline), source, decode, convert, sink, NULL);
// link source to decode
gst_element_link(source, decode);
// caps
GstCaps *caps;
caps = gst_caps_new_simple("audio/x-raw-int",
"rate", G_TYPE_INT, rate,
"channels", G_TYPE_INT, channels,
"width", G_TYPE_INT, depth,
"depth", G_TYPE_INT, depth,
"signed", G_TYPE_BOOLEAN, output_signed,
NULL);
// link convert to sink
gst_element_link_filtered(convert, sink, caps);
gst_caps_unref(caps);
// start playing
gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING);
// iterate
g_print("Running...\n");
g_main_loop_run(loop);
// out of the main loop, clean up nicely
g_print("Returned, stopping playback\n");
gst_element_set_state(pipeline, GST_STATE_NULL);
g_print("Deleting pipeline\n");
gst_object_unref(GST_OBJECT(pipeline));
g_source_remove (bus_watch_id);
g_main_loop_unref(loop);
// get data
g_print("get data\n");
out_data = g_memory_output_stream_get_data(G_MEMORY_OUTPUT_STREAM(stream));
unsigned long size = g_memory_output_stream_get_size(G_MEMORY_OUTPUT_STREAM(stream));
unsigned long sizeData = g_memory_output_stream_get_data_size(G_MEMORY_OUTPUT_STREAM(stream));
std::cout << "stream size: " << size << std::endl;
std::cout << "stream data size: " << sizeData << std::endl;
// access data and store in vector
std::vector<int16_t> data;
for (unsigned long i = 0; i < sizeData/2; ++i) {
data.push_back(((gint16*)out_data)[i]);
}
return 0;
}
Related
I'm trying to direct the output from opengl into a mp4 file.
Currently, I'm getting the error "Invalid input" from the call avcodec_send_frame(c, frame). Why am I getting this error?
class VideoCapture2
{
public:
VideoCapture2(const char *filename, unsigned int width, unsigned int height, int framerate, unsigned int bitrate){
avformat_alloc_output_context2(&avFormatContext, NULL, NULL, filename);
if (!avFormatContext) {
printf("Could not deduce output format from file extension: using MPEG.\n");
avformat_alloc_output_context2(&avFormatContext, NULL, "mpeg", filename);
}
if (!avFormatContext)
exit(1);
avOutputFormat = avFormatContext->oformat;
// Video Stream
/* find the encoder */
AVCodecID codec_id = AV_CODEC_ID_H264;
codec = avcodec_find_encoder(codec_id);
if (!codec) {
fprintf(stderr, "Could not find encoder for '%s'\n",
avcodec_get_name(codec_id));
exit(1);
}
pkt = av_packet_alloc();
if (!pkt) {
fprintf(stderr, "Could not allocate AVPacket\n");
exit(1);
}
avStream = avformat_new_stream(avFormatContext, NULL);
if (!avStream) {
fprintf(stderr, "Could not allocate stream\n");
exit(1);
}
avStream->id = avFormatContext->nb_streams-1;
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
fprintf(stderr, "Could not alloc an encoding context\n");
exit(1);
}
codec_ctx->codec_id = codec_id;
/* put sample parameters */
codec_ctx->bit_rate = bitrate;
/* resolution must be a multiple of two */
if(width % 2 != 0)
throw std::invalid_argument( "The width must be devisible by two" );
if(height % 2 != 0)
throw std::invalid_argument( "The height must be devisible by two" );
codec_ctx->width = width;
codec_ctx->height = height;
/* frames per second */
codec_ctx->framerate = (AVRational){framerate, 1};
/* timebase: This is the fundamental unit of time (in seconds) in terms
* of which frame timestamps are represented. For fixed-fps content,
* timebase should be 1/framerate and timestamp increments should be
* identical to 1. */
avStream->time_base = (AVRational){ 1, framerate };
codec_ctx->time_base = avStream->time_base;
codec_ctx->gop_size = 10; /* emit one intra frame every twelve frames at most */
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
/* Some formats want stream headers to be separate. */
if (avOutputFormat->flags & AVFMT_GLOBALHEADER)
codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
frame = alloc_frame(codec_ctx->pix_fmt, codec_ctx->width, codec_ctx->height);
if (!frame) {
fprintf(stderr, "Could not allocate video frame\n");
exit(1);
}
/* copy the stream parameters to the muxer */
ret = avcodec_parameters_from_context(avStream->codecpar, codec_ctx);
if (ret < 0) {
fprintf(stderr, "Could not copy the stream parameters\n");
exit(1);
}
// Color format Conversion
sws = sws_getContext( codec_ctx->width
, codec_ctx->height
, AV_PIX_FMT_RGB32
, codec_ctx->width
, codec_ctx->height
, AV_PIX_FMT_YUV420P
, SWS_FAST_BILINEAR // Change this???
, 0, 0, 0);
// Check output file
av_dump_format(avFormatContext, 0, filename, 1);
/* open the output file, if needed */
if (!(avOutputFormat->flags & AVFMT_NOFILE)) {
ret = avio_open(&avFormatContext->pb, filename, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "Could not open '%s': %s\n", filename,
av_err2str(ret));
exit(1);
}
}
/* Write the stream header, if any. */
ret = avformat_write_header(avFormatContext, &avDict);
if (ret < 0) {
fprintf(stderr, "Error occurred when opening output file: %s\n",
av_err2str(ret));
exit(1);
}
}
void addFrame(){
fflush(stdout);
/* Make sure the frame data is writable.
On the first round, the frame is fresh from av_frame_get_buffer()
and therefore we know it is writable.
But on the next rounds, encode() will have called
avcodec_send_frame(), and the codec may have kept a reference to
the frame in its internal structures, that makes the frame
unwritable.
av_frame_make_writable() checks that and allocates a new buffer
for the frame only if necessary.
*/
ret = av_frame_make_writable(frame);
if (ret < 0){
fprintf(stderr, "Could not make the frame writable\n");
exit(1); // Wait... you should throw error instead!
}
size_t nvals = 4 * codec_ctx->width * codec_ctx->height; //GL_BGRA
pixels = (GLubyte *) realloc(pixels, nvals * sizeof(GLubyte)); // I don't think I need to do this every time since the size is constant
glReadPixels(0, 0, codec_ctx->width, codec_ctx->height, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
// CONVERT TO YUV AND ENCODE
ret = av_image_alloc(frame->data, frame->linesize, codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P, 32);
if (ret < 0){
fprintf(stderr, "Could not allocate the image\n");
exit(1); // Wait... you should throw error instead!
}
// Compensate for OpenGL y-axis pointing upwards and ffmpeg y-axis pointing downwards
uint8_t *in_data[1] = {(uint8_t *) pixels + (codec_ctx->height-1)*codec_ctx->width*4}; // address of the last line
int in_linesize[1] = {- codec_ctx->width * 4}; // negative stride
sws_scale(sws, in_data, in_linesize, 0, codec_ctx->height, frame->data, frame->linesize);
frame->pts = frame_order;
frame_order++;
/* encode the image */
write_frame(avFormatContext, codec_ctx, avStream, frame, pkt);
}
void close()
{
write_frame(avFormatContext, codec_ctx, avStream, NULL, pkt);
av_write_trailer(avFormatContext);
avcodec_free_context(&codec_ctx);
av_frame_free(&frame);
sws_freeContext(sws);
if (!(avFormatContext->oformat->flags & AVFMT_NOFILE))
/* Close the output file. */
avio_closep(&avFormatContext->pb);
avformat_free_context(avFormatContext);
}
private:
AVOutputFormat *avOutputFormat;
AVFormatContext* avFormatContext = NULL;
AVStream* avStream;
AVDictionary *avDict = NULL; // "create" an empty dictionary
GLubyte *pixels = NULL;
struct SwsContext *sws;
const AVCodec *codec;
AVCodecContext *codec_ctx= NULL;
// Should be ref counted??? https://ffmpeg.org/doxygen/3.3/group__lavc__encdec.html
AVFrame *frame;
AVPacket *pkt;
//
int frame_order, ret;
int write_frame(AVFormatContext *fmt_ctx, AVCodecContext *c,
AVStream *st, AVFrame *frame, AVPacket *pkt)
{
int ret;
// ERROR OCCURS HERE
ret = avcodec_send_frame(c, frame);
// ERROR OCCURS HERE
if (ret < 0) {
fprintf(stderr, "Error sending a frame to the encoder: %s\n",
av_err2str(ret));
exit(1);
}
while (ret >= 0) {
ret = avcodec_receive_packet(c, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
else if (ret < 0) {
fprintf(stderr, "Error encoding a frame: %s\n", av_err2str(ret));
exit(1);
}
/* rescale output packet timestamp values from codec to stream timebase */
av_packet_rescale_ts(pkt, c->time_base, st->time_base);
pkt->stream_index = st->index;
/* Write the compressed frame to the media file. */
log_packet(fmt_ctx, pkt);
ret = av_interleaved_write_frame(fmt_ctx, pkt);
/* pkt is now blank (av_interleaved_write_frame() takes ownership of
* its contents and resets pkt), so that no unreferencing is necessary.
* This would be different if one used av_write_frame(). */
if (ret < 0) {
fprintf(stderr, "Error while writing output packet: %s\n", av_err2str(ret));
exit(1);
}
}
return ret == AVERROR_EOF ? 1 : 0;
}
void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt)
{
AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;
printf("pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),
av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),
av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),
pkt->stream_index);
}
AVFrame *alloc_frame(enum AVPixelFormat pix_fmt, int width, int height)
{
AVFrame *frame;
int ret;
frame = av_frame_alloc();
if (!frame)
return NULL;
frame->format = pix_fmt;
frame->width = width;
frame->height = height;
/* allocate the buffers for the frame data */
ret = av_frame_get_buffer(frame, 0);
if (ret < 0) {
fprintf(stderr, "Could not allocate frame data.\n");
exit(1);
}
return frame;
}
};
The issue turned out to be that I had missed calling avcodec_open2(..) now it works :)
/* open the codec */
AVDictionary *opt = NULL;
av_dict_copy(&opt, avDict, 0);
ret = avcodec_open2(codec_ctx, codec, &opt);
av_dict_free(&opt);
if (ret < 0) {
fprintf(stderr, "Could not open video codec: %s\n", av_err2str(ret));
exit(1);
}
// Then allocate frame...
frame = alloc_frame(codec_ctx->pix_fmt, codec_ctx->width, codec_ctx->height);
if (!frame) {
fprintf(stderr, "Could not allocate video frame\n");
exit(1);
}
For reference, this is the full code (but the video quality is shit so you will have to tune that yourself)
#ifndef VIDEO_CAPTURE2_H
#define VIDEO_CAPTURE2_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../include/glad/glad.h"
#include "finite_math.hpp"
#include <stdexcept>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
}
// These exist to patch three functions for which gcc gets compiler errors
#ifdef av_err2str
#undef av_err2str
#include <string>
av_always_inline std::string av_err2string(int errnum) {
char str[AV_ERROR_MAX_STRING_SIZE];
return av_make_error_string(str, AV_ERROR_MAX_STRING_SIZE, errnum);
}
#define av_err2str(err) av_err2string(err).c_str()
#endif
#ifdef av_ts2str
#undef av_ts2str
#include <string>
av_always_inline std::string av_ts2string(int ts) {
char str[AV_TS_MAX_STRING_SIZE];
return av_ts_make_string(str, ts);
}
#define av_ts2str(ts) av_ts2string(ts).c_str()
#endif
#ifdef av_ts2timestr
#undef av_ts2timestr
#include <string>
av_always_inline std::string av_ts2timestring(int ts, AVRational *tb) {
char str[AV_TS_MAX_STRING_SIZE];
return av_ts_make_time_string(str, ts, tb);
}
#define av_ts2timestr(ts, tb) av_ts2timestring(ts, tb).c_str()
#endif
class VideoCapture2
{
public:
VideoCapture2(const char *filename, unsigned int width, unsigned int height, int framerate, unsigned int bitrate){
avformat_alloc_output_context2(&avFormatContext, NULL, NULL, filename);
if (!avFormatContext) {
printf("Could not deduce output format from file extension: using MPEG.\n");
avformat_alloc_output_context2(&avFormatContext, NULL, "mpeg", filename);
}
if (!avFormatContext)
exit(1);
avOutputFormat = avFormatContext->oformat;
// Video Stream
/* find the mpeg1video encoder */
/* find the encoder */
AVCodecID codec_id = AV_CODEC_ID_H264;
codec = avcodec_find_encoder(codec_id);
if (!codec) {
fprintf(stderr, "Could not find encoder for '%s'\n",
avcodec_get_name(codec_id));
exit(1);
}
pkt = av_packet_alloc();
if (!pkt) {
fprintf(stderr, "Could not allocate AVPacket\n");
exit(1);
}
avStream = avformat_new_stream(avFormatContext, NULL);
if (!avStream) {
fprintf(stderr, "Could not allocate stream\n");
exit(1);
}
avStream->id = avFormatContext->nb_streams-1;
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
fprintf(stderr, "Could not alloc an encoding context\n");
exit(1);
}
codec_ctx->codec_id = codec_id;
/* put sample parameters */
codec_ctx->bit_rate = bitrate;
/* resolution must be a multiple of two */
if(width % 2 != 0)
throw std::invalid_argument( "The width must be devisible by two" );
if(height % 2 != 0)
throw std::invalid_argument( "The height must be devisible by two" );
codec_ctx->width = width;
codec_ctx->height = height;
/* frames per second */
codec_ctx->framerate = (AVRational){framerate, 1};
/* timebase: This is the fundamental unit of time (in seconds) in terms
* of which frame timestamps are represented. For fixed-fps content,
* timebase should be 1/framerate and timestamp increments should be
* identical to 1. */
avStream->time_base = (AVRational){ 1, framerate };
codec_ctx->time_base = avStream->time_base;
codec_ctx->gop_size = 10; /* emit one intra frame every twelve frames at most */
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
/* Some formats want stream headers to be separate. */
if (avOutputFormat->flags & AVFMT_GLOBALHEADER)
codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
/* open the codec */
AVDictionary *opt = NULL;
av_dict_copy(&opt, avDict, 0);
ret = avcodec_open2(codec_ctx, codec, &opt);
av_dict_free(&opt);
if (ret < 0) {
fprintf(stderr, "Could not open video codec: %s\n", av_err2str(ret));
exit(1);
}
frame = alloc_frame(codec_ctx->pix_fmt, codec_ctx->width, codec_ctx->height);
if (!frame) {
fprintf(stderr, "Could not allocate video frame\n");
exit(1);
}
/* copy the stream parameters to the muxer */
ret = avcodec_parameters_from_context(avStream->codecpar, codec_ctx);
if (ret < 0) {
fprintf(stderr, "Could not copy the stream parameters\n");
exit(1);
}
// Color fromat COnversion
sws = sws_getContext( codec_ctx->width
, codec_ctx->height
, AV_PIX_FMT_RGB32
, codec_ctx->width
, codec_ctx->height
, AV_PIX_FMT_YUV420P
, SWS_FAST_BILINEAR // Change this???
, 0, 0, 0);
// Check output file
av_dump_format(avFormatContext, 0, filename, 1);
/* open the output file, if needed */
if (!(avOutputFormat->flags & AVFMT_NOFILE)) {
ret = avio_open(&avFormatContext->pb, filename, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "Could not open '%s': %s\n", filename,
av_err2str(ret));
exit(1);
}
}
/* Write the stream header, if any. */
ret = avformat_write_header(avFormatContext, &avDict);
if (ret < 0) {
fprintf(stderr, "Error occurred when opening output file: %s\n",
av_err2str(ret));
exit(1);
}
}
void addFrame(){
fflush(stdout);
/* Make sure the frame data is writable.
On the first round, the frame is fresh from av_frame_get_buffer()
and therefore we know it is writable.
But on the next rounds, encode() will have called
avcodec_send_frame(), and the codec may have kept a reference to
the frame in its internal structures, that makes the frame
unwritable.
av_frame_make_writable() checks that and allocates a new buffer
for the frame only if necessary.
*/
ret = av_frame_make_writable(frame);
if (ret < 0){
fprintf(stderr, "Could not make the frame writable\n");
exit(1); // Wait... you should throw error instead!
}
size_t nvals = 4 * codec_ctx->width * codec_ctx->height; //GL_BGRA
pixels = (GLubyte *) realloc(pixels, nvals * sizeof(GLubyte)); // I don't think I need to do this every time since the size is constant
glReadPixels(0, 0, codec_ctx->width, codec_ctx->height, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
// CONVERT TO YUV AND ENCODE
ret = av_image_alloc(frame->data, frame->linesize, codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P, 32);
if (ret < 0){
fprintf(stderr, "Could not allocate the image\n");
exit(1); // Wait... you should throw error instead!
}
// Compensate for OpenGL y-axis pointing upwards and ffmpeg y-axis pointing downwards
uint8_t *in_data[1] = {(uint8_t *) pixels + (codec_ctx->height-1)*codec_ctx->width*4}; // address of the last line
int in_linesize[1] = {- codec_ctx->width * 4}; // negative stride
sws_scale(sws, in_data, in_linesize, 0, codec_ctx->height, frame->data, frame->linesize);
frame->pts = frame_order;
frame_order++;
/* encode the image */
write_frame(avFormatContext, codec_ctx, avStream, frame, pkt);
}
void close()
{
write_frame(avFormatContext, codec_ctx, avStream, NULL, pkt);
av_write_trailer(avFormatContext);
avcodec_free_context(&codec_ctx);
av_frame_free(&frame);
sws_freeContext(sws);
if (!(avFormatContext->oformat->flags & AVFMT_NOFILE))
/* Close the output file. */
avio_closep(&avFormatContext->pb);
avformat_free_context(avFormatContext);
}
private:
AVOutputFormat *avOutputFormat;
AVFormatContext* avFormatContext = NULL;
AVStream* avStream;
AVDictionary *avDict = NULL; // "create" an empty dictionary
GLubyte *pixels = NULL;
struct SwsContext *sws;
const AVCodec *codec;
AVCodecContext *codec_ctx= NULL;
// Should be ref counted??? https://ffmpeg.org/doxygen/3.3/group__lavc__encdec.html
AVFrame *frame;
AVPacket *pkt;
//
int frame_order, ret;
int write_frame(AVFormatContext *fmt_ctx, AVCodecContext *c,
AVStream *st, AVFrame *frame, AVPacket *pkt)
{
int ret;
// send the frame to the encoder
ret = avcodec_send_frame(c, frame);
if (ret < 0) {
fprintf(stderr, "Error sending a frame to the encoder: %s\n",
av_err2str(ret));
exit(1);
}
while (ret >= 0) {
ret = avcodec_receive_packet(c, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
else if (ret < 0) {
fprintf(stderr, "Error encoding a frame: %s\n", av_err2str(ret));
exit(1);
}
/* rescale output packet timestamp values from codec to stream timebase */
av_packet_rescale_ts(pkt, c->time_base, st->time_base);
pkt->stream_index = st->index;
/* Write the compressed frame to the media file. */
log_packet(fmt_ctx, pkt);
ret = av_interleaved_write_frame(fmt_ctx, pkt);
/* pkt is now blank (av_interleaved_write_frame() takes ownership of
* its contents and resets pkt), so that no unreferencing is necessary.
* This would be different if one used av_write_frame(). */
if (ret < 0) {
fprintf(stderr, "Error while writing output packet: %s\n", av_err2str(ret));
exit(1);
}
}
return ret == AVERROR_EOF ? 1 : 0;
}
void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt)
{
AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;
printf("pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),
av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),
av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),
pkt->stream_index);
}
AVFrame *alloc_frame(enum AVPixelFormat pix_fmt, int width, int height)
{
AVFrame *frame;
int ret;
frame = av_frame_alloc();
if (!frame)
return NULL;
frame->format = pix_fmt;
frame->width = width;
frame->height = height;
/* allocate the buffers for the frame data */
ret = av_frame_get_buffer(frame, 0);
if (ret < 0) {
fprintf(stderr, "Could not allocate frame data.\n");
exit(1);
}
return frame;
}
};
#endif
I have found that if I send the same OGG/Vorbis audio into a gstreamer 1.0 pipeline within a program twice I get different audio out (similar, but not identical). I have tried unreferencing the pipeline and rebuilding it between uses, but too no avail. Something seems to maintain some sort of state. The second version is the same as other second versions, with the same differences to the first.
I'm looking for reproducable results from a server processing audio, and this is getting in the way.
I'm sorry this is so long. Getting it to reproduce and rebuild to pipeline seemed like a relevant thing to leave in. I have reproduced this with several OGG/vorbis files (not empty though). Call the file "a.ogg" and then running the program will produce "job1.raw" and "job2.raw" which have been different each time.
Thanks for any help,
Richard.
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <glib.h>
#include <gst/gst.h>
typedef struct _Decoder2Data {
GstElement * appsrc;
GstElement * decodebin;
GstElement * audioconvert;
GstElement * audioresample;
GstElement * queue1;
GstElement * filesink;
GstElement * pipeline;
GstBus *bus;
const char* request_id;
const char* outdir;
GMainLoop * main_loop;
} Decoder2Data;
void start_request(const char* caps_str, Decoder2Data * data);
void process_data(Decoder2Data * obj, char * audio, int audioSize);
void end_request(Decoder2Data* data);
void finish_request(Decoder2Data * data);
int create_pipeline(int argc, char *argv[], Decoder2Data * data);
void closeGstreamer(Decoder2Data * data);
void *g_loop_thread(void *ptr);
void start_request(const char* caps_str, Decoder2Data * data) {
g_printerr("Test %s: Starting request\n", data->request_id);
g_object_set(data->appsrc, "caps", NULL, NULL);
if (data->outdir) {
char path[128];
sprintf(path, "%s/%s.raw", data->outdir, data->request_id);
FILE *fp = fopen(path, "w+");
if(fp != NULL) {
fclose(fp);
gst_element_set_state(data->pipeline, GST_STATE_PAUSED);
gst_element_set_state(data->filesink, GST_STATE_NULL);
g_object_set(data->filesink, "location", path, NULL);
gst_element_set_state(data->filesink, GST_STATE_PLAYING);
} else {
g_warning("Test %s: Unable to open raw audio file %s.\n", data->request_id, path);
}
}
gst_element_set_state(data->pipeline, GST_STATE_PLAYING);
gst_element_set_state(data->filesink, GST_STATE_PLAYING);
g_printerr("Test Started request\n");
}
void process_data(Decoder2Data * obj, char * audio, int audioSize) {
GstFlowReturn ret;
GstBuffer * buf = gst_buffer_new_and_alloc(audioSize);
gst_buffer_fill(buf, 0, audio, audioSize);
g_signal_emit_by_name (obj->appsrc, "push-buffer", buf, &ret);
if(ret != GST_FLOW_OK)
g_warning("Test Pushing audio resulted in flow state %d\n", ret);
}
void end_request(Decoder2Data* data) {
GstFlowReturn ret;
g_signal_emit_by_name (data->appsrc, "end-of-stream", &ret);
}
GstElement * createElement(const char* name, const char* factoryName) {
GstElement * ret = gst_element_factory_make(name, factoryName);
if (!ret)
g_printerr ("Test failed to create element of type '%s'\n", name);
return ret;
}
// Handler for the pad-added signal
static void _connect_decoder(GstElement *src, GstPad *pad, Decoder2Data *data) {
g_debug("Test _connect_decoder\n");
GstPad * sink_pad = gst_element_get_static_pad (data->audioconvert, "sink");
GstPadLinkReturn ret = gst_pad_link(pad, sink_pad);
if (GST_PAD_LINK_FAILED (ret))
g_printerr("Test Link failed with GstPadLinkError %d.\n", ret);
else
g_debug("Test Link succeeded.\n");
g_debug("Test Connected audio decoder\n");
}
void gstLink(GstElement * src, GstElement * target) {
if (!gst_element_link (src, target))
g_printerr ("Test ----------------- elements could not be linked.\n");
}
static void _on_eos(GstElement *src, GstPad *pad, Decoder2Data *data) {
g_debug("Test _on_eos\n");
finish_request(data);
}
static void _on_error(GstElement *src, GstMessage *pad, Decoder2Data *data) {
g_debug("Test _on_error\n");
GError *err = NULL;
gchar *dbg_info = NULL;
gst_message_parse_error (pad, &err, &dbg_info);
if (err) {
size_t len = strlen(err->message);
g_printerr("ERROR: %s", err->message);
}
g_error_free(err);
g_free(dbg_info);
finish_request(data);
}
void create_and_link(Decoder2Data * data) {
data->appsrc = createElement("appsrc", "appsrc");
data->decodebin = createElement("decodebin", "decodebin");
data->audioconvert = createElement("audioconvert", "audioconvert");
data->audioresample = createElement("audioresample", "audioresample");
data->queue1 = createElement("capsfilter", "capsfilter");
data->filesink = createElement("filesink", "filesink");
g_object_set (data->appsrc, "is-live", TRUE, NULL);
const gchar *caps_str = "audio/x-raw, channels=1, rate=16000, format=S16LE";
GstCaps * caps = gst_caps_from_string(caps_str);
g_object_set (data->queue1, "caps", caps, NULL);
g_object_set (data->filesink, "location", "/dev/null", NULL);
g_debug("Test Created GStreamer elements");
data->pipeline = gst_pipeline_new("pipeline");
if (!data->pipeline) {
g_printerr ("Test pipe line could not be created.\n");
}
// Add all elements to the pipeline
gst_bin_add_many (GST_BIN (data->pipeline), data->appsrc, data->decodebin, data->audioconvert, data->audioresample, data->queue1, data->filesink, NULL);
gstLink(data->appsrc, data->decodebin);
g_signal_connect(data->decodebin, "pad-added", G_CALLBACK (_connect_decoder), data);
gstLink(data->audioconvert, data->audioresample);
gstLink(data->audioresample, data->queue1);
gstLink(data->queue1, data->filesink);
g_debug("Linked GStreamer elements\n");
// Create bus
data->bus = gst_element_get_bus(data->pipeline);
gst_bus_add_signal_watch(data->bus);
gst_bus_enable_sync_message_emission(data->bus);
gst_pipeline_use_clock((GstPipeline*)data->pipeline, (GstClock*)NULL);
g_signal_connect(data->bus, "message::eos", G_CALLBACK(_on_eos), data);
g_signal_connect(data->bus, "message::error", G_CALLBACK(_on_error), data);
}
void finish_request(Decoder2Data * data) {
g_printerr("Test finish_request %s\n", data->request_id);
if (data->outdir) {
gst_element_set_state(data->filesink, GST_STATE_NULL);
g_object_set(data->filesink, "location", "/dev/null", NULL);
gst_element_set_state(data->filesink, GST_STATE_PLAYING);
}
gst_element_set_state(data->pipeline, GST_STATE_NULL);
// Destroy the old pipeline.
gst_element_set_state(data->appsrc, GST_STATE_NULL);
gst_element_set_state(data->decodebin, GST_STATE_NULL);
gst_element_set_state(data->audioconvert, GST_STATE_NULL);
gst_element_set_state(data->audioresample, GST_STATE_NULL);
gst_element_set_state(data->queue1, GST_STATE_NULL);
gst_element_set_state(data->filesink, GST_STATE_NULL);
gst_object_unref(data->pipeline);
// Build a new pipeline
create_and_link(data);
gst_element_set_state(data->pipeline, GST_STATE_READY);
g_printerr("Rebuilt pipeline.");
g_printerr("Finished request complete.\n");
}
int create_pipeline(int argc, char *argv[], Decoder2Data * data) {
g_printerr("Test create_pipeline\n");
gst_init (&argc, &argv);
data->request_id = "<undefined>";
data->outdir = "./";
create_and_link(data);
g_debug("Setting pipeline to READY\n");
gst_element_set_state(data->pipeline, GST_STATE_READY);
g_debug("Set pipeline to READY\n");
return 0;
}
void closeGstreamer(Decoder2Data * data) {
gst_object_unref (data->bus);
gst_element_set_state (data->pipeline, GST_STATE_NULL);
gst_object_unref (data->pipeline);
}
#include <pthread.h>
void *g_loop_thread(void *ptr) {
g_debug("Test main loop thread started\n");
Decoder2Data * data = (Decoder2Data*) ptr;
data->main_loop = g_main_loop_new(NULL, FALSE);
g_debug("Test main loop created, executing g_main_loop_run\n");
g_main_loop_run(data->main_loop); // This is blocking
g_debug("Test main loop thread ENDED\n");
return NULL;
}
int main(int argc, char *argv[]) {
Decoder2Data data;
memset (&data, 0, sizeof (data));
create_pipeline(argc, argv, &data);
pthread_t thread;
int ret = pthread_create(&thread, NULL, g_loop_thread, &data);
if (ret != 0) {
g_printerr("Test Thread not started");
return -1;
}
usleep(250000); // Wait a bit to make sure the thread started
g_printerr("Test starting test\n");
data.request_id = "job1";
start_request("", &data);
FILE * file = fopen("./a.ogg", "rb");
int size = 86*1024/8/4;
char buffer[size];
int n;
while ((n = fread(buffer, 1, size, file)) > 0) {
g_printerr("read %d of data\n", n);
process_data(&data, buffer, n);
}
fclose(file);
g_printerr("finished reading data\n");
end_request(&data);
usleep(250000);
finish_request(&data);
// Switch to second request and do it again.
data.request_id = "job2";
start_request("", &data);
file = fopen("./a.ogg", "rb");
while ((n = fread(buffer, 1, size, file)) > 0) {
g_printerr("read %d of data\n", n);
process_data(&data, buffer, n);
}
fclose(file);
g_printerr("finished reading data again\n");
end_request(&data);
usleep(250000);
finish_request(&data);
g_printerr("waiting for the gstreamer thread to end...\n");
g_main_loop_quit (data.main_loop);
pthread_join(thread, NULL);
g_printerr("Closing\n");
closeGstreamer(&data);
g_printerr("Exit OK\n");
return 0;
}
I want to apply processing to a video clip with sound track, extract and process frame by frame and write result to output file. Number of frames, size of frame and speed remains unchanged in output clip. Also I want to keep the same audio track as I have in source.
I can read clip, decode frames and process then using opencv. Audio packets are also writes fine. I'm stuck on forming output video stream.
The minimal runnable code I have for now (sorry it not so short, but cant do it shorter):
extern "C" {
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include "libavcodec/avcodec.h"
#include <libavutil/opt.h>
#include <libavdevice/avdevice.h>
#include <libswscale/swscale.h>
}
#include "opencv2/opencv.hpp"
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#endif
using namespace std;
using namespace cv;
static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, const char *tag)
{
AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;
char buf1[AV_TS_MAX_STRING_SIZE] = { 0 };
av_ts_make_string(buf1, pkt->pts);
char buf2[AV_TS_MAX_STRING_SIZE] = { 0 };
av_ts_make_string(buf1, pkt->dts);
char buf3[AV_TS_MAX_STRING_SIZE] = { 0 };
av_ts_make_string(buf1, pkt->duration);
char buf4[AV_TS_MAX_STRING_SIZE] = { 0 };
av_ts_make_time_string(buf1, pkt->pts, time_base);
char buf5[AV_TS_MAX_STRING_SIZE] = { 0 };
av_ts_make_time_string(buf1, pkt->dts, time_base);
char buf6[AV_TS_MAX_STRING_SIZE] = { 0 };
av_ts_make_time_string(buf1, pkt->duration, time_base);
printf("pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
buf1, buf4,
buf2, buf5,
buf3, buf6,
pkt->stream_index);
}
int main(int argc, char **argv)
{
AVOutputFormat *ofmt = NULL;
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
AVPacket pkt;
AVFrame *pFrame = NULL;
AVFrame *pFrameRGB = NULL;
int frameFinished = 0;
pFrame = av_frame_alloc();
pFrameRGB = av_frame_alloc();
const char *in_filename, *out_filename;
int ret, i;
in_filename = "../../TestClips/Audio Video Sync Test.mp4";
out_filename = "out.mp4";
// Initialize FFMPEG
av_register_all();
// Get input file format context
if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0)
{
fprintf(stderr, "Could not open input file '%s'", in_filename);
goto end;
}
// Extract streams description
if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0)
{
fprintf(stderr, "Failed to retrieve input stream information");
goto end;
}
// Print detailed information about the input or output format,
// such as duration, bitrate, streams, container, programs, metadata, side data, codec and time base.
av_dump_format(ifmt_ctx, 0, in_filename, 0);
// Allocate an AVFormatContext for an output format.
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
if (!ofmt_ctx)
{
fprintf(stderr, "Could not create output context\n");
ret = AVERROR_UNKNOWN;
goto end;
}
// The output container format.
ofmt = ofmt_ctx->oformat;
// Allocating output streams
for (i = 0; i < ifmt_ctx->nb_streams; i++)
{
AVStream *in_stream = ifmt_ctx->streams[i];
AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
if (!out_stream)
{
fprintf(stderr, "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
if (ret < 0)
{
fprintf(stderr, "Failed to copy context from input to output stream codec context\n");
goto end;
}
out_stream->codec->codec_tag = 0;
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
{
out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
}
// Show output format info
av_dump_format(ofmt_ctx, 0, out_filename, 1);
// Open output file
if (!(ofmt->flags & AVFMT_NOFILE))
{
ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
if (ret < 0)
{
fprintf(stderr, "Could not open output file '%s'", out_filename);
goto end;
}
}
// Write output file header
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0)
{
fprintf(stderr, "Error occurred when opening output file\n");
goto end;
}
// Search for input video codec info
AVCodec *in_codec = nullptr;
AVCodecContext* avctx = nullptr;
int video_stream_index = -1;
for (int i = 0; i < ifmt_ctx->nb_streams; i++)
{
if (ifmt_ctx->streams[i]->codec->coder_type == AVMEDIA_TYPE_VIDEO)
{
video_stream_index = i;
avctx = ifmt_ctx->streams[i]->codec;
in_codec = avcodec_find_decoder(avctx->codec_id);
if (!in_codec)
{
fprintf(stderr, "in codec not found\n");
exit(1);
}
break;
}
}
// Search for output video codec info
AVCodec *out_codec = nullptr;
AVCodecContext* o_avctx = nullptr;
int o_video_stream_index = -1;
for (int i = 0; i < ofmt_ctx->nb_streams; i++)
{
if (ofmt_ctx->streams[i]->codec->coder_type == AVMEDIA_TYPE_VIDEO)
{
o_video_stream_index = i;
o_avctx = ofmt_ctx->streams[i]->codec;
out_codec = avcodec_find_encoder(o_avctx->codec_id);
if (!out_codec)
{
fprintf(stderr, "out codec not found\n");
exit(1);
}
break;
}
}
// openCV pixel format
AVPixelFormat pFormat = AV_PIX_FMT_RGB24;
// Data size
int numBytes = avpicture_get_size(pFormat, avctx->width, avctx->height);
// allocate buffer
uint8_t *buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
// fill frame structure
avpicture_fill((AVPicture *)pFrameRGB, buffer, pFormat, avctx->width, avctx->height);
// frame area
int y_size = avctx->width * avctx->height;
// Open input codec
avcodec_open2(avctx, in_codec, NULL);
// Main loop
while (1)
{
AVStream *in_stream, *out_stream;
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < 0)
{
break;
}
in_stream = ifmt_ctx->streams[pkt.stream_index];
out_stream = ofmt_ctx->streams[pkt.stream_index];
log_packet(ifmt_ctx, &pkt, "in");
// copy packet
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AVRounding(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AVRounding(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;
log_packet(ofmt_ctx, &pkt, "out");
if (pkt.stream_index == video_stream_index)
{
avcodec_decode_video2(avctx, pFrame, &frameFinished, &pkt);
if (frameFinished)
{
struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getCachedContext(NULL,
avctx->width,
avctx->height,
avctx->pix_fmt,
avctx->width,
avctx->height,
AV_PIX_FMT_BGR24,
SWS_BICUBIC,
NULL,
NULL,
NULL);
sws_scale(img_convert_ctx,
((AVPicture*)pFrame)->data,
((AVPicture*)pFrame)->linesize,
0,
avctx->height,
((AVPicture *)pFrameRGB)->data,
((AVPicture *)pFrameRGB)->linesize);
sws_freeContext(img_convert_ctx);
// Do some image processing
cv::Mat img(pFrame->height, pFrame->width, CV_8UC3, pFrameRGB->data[0],false);
cv::GaussianBlur(img,img,Size(5,5),3);
cv::imshow("Display", img);
cv::waitKey(5);
// --------------------------------
// Transform back to initial format
// --------------------------------
img_convert_ctx = sws_getCachedContext(NULL,
avctx->width,
avctx->height,
AV_PIX_FMT_BGR24,
avctx->width,
avctx->height,
avctx->pix_fmt,
SWS_BICUBIC,
NULL,
NULL,
NULL);
sws_scale(img_convert_ctx,
((AVPicture*)pFrameRGB)->data,
((AVPicture*)pFrameRGB)->linesize,
0,
avctx->height,
((AVPicture *)pFrame)->data,
((AVPicture *)pFrame)->linesize);
// --------------------------------------------
// Something must be here
// --------------------------------------------
//
// Write fideo frame (How to write frame to output stream ?)
//
// --------------------------------------------
sws_freeContext(img_convert_ctx);
}
}
else // write sound frame
{
ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
}
if (ret < 0)
{
fprintf(stderr, "Error muxing packet\n");
break;
}
// Decrease packet ref counter
av_packet_unref(&pkt);
}
av_write_trailer(ofmt_ctx);
end:
avformat_close_input(&ifmt_ctx);
// close output
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
{
avio_closep(&ofmt_ctx->pb);
}
avformat_free_context(ofmt_ctx);
if (ret < 0 && ret != AVERROR_EOF)
{
char buf_err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
av_make_error_string(buf_err, AV_ERROR_MAX_STRING_SIZE, ret);
fprintf(stderr, "Error occurred: %s\n", buf_err);
return 1;
}
avcodec_close(avctx);
av_free(pFrame);
av_free(pFrameRGB);
return 0;
}
Your original code segfaults in my case. Initializing the output codec context seems to fix it. The code below works for me but I didn't test the OpenCV stuff as I don't have the lib installed.
Get the codec context:
// Search for output video codec info
AVCodec *out_codec = NULL;
AVCodecContext* o_avctx = NULL;
int o_video_stream_index = -1;
for (int i = 0; i < ofmt_ctx->nb_streams; i++)
{
if (ofmt_ctx->streams[i]->codec->coder_type == AVMEDIA_TYPE_VIDEO)
{
o_video_stream_index = i;
out_codec = avcodec_find_encoder(ofmt_ctx->streams[i]->codec->codec_id);
o_avctx = avcodec_alloc_context3(out_codec);
o_avctx->height = avctx->height;
o_avctx->width = avctx->width;
o_avctx->sample_aspect_ratio = avctx->sample_aspect_ratio;
if (out_codec->pix_fmts)
o_avctx->pix_fmt = out_codec->pix_fmts[0];
else
o_avctx->pix_fmt = avctx->pix_fmt;
o_avctx->time_base = avctx->time_base;
avcodec_open2(o_avctx, out_codec, NULL);
}
}
Encode and write:
// Main loop
while (1)
{
...
if (pkt.stream_index == video_stream_index)
{
avcodec_decode_video2(avctx, pFrame, &frameFinished, &pkt);
if (frameFinished)
{
...
// --------------------------------------------
// Something must be here
// --------------------------------------------
int got_packet = 0;
AVPacket enc_pkt = { 0 };
av_init_packet(&enc_pkt);
avcodec_encode_video2(o_avctx, &enc_pkt, pFrame, &got_packet);
av_interleaved_write_frame(ofmt_ctx, &enc_pkt);
....
}
}
you should assign processed frame's packets information to your Original packets then pass it to av_interleaved_write_frame
I have an object to generate videos with GStreamer. Each time I want to generate a new video y create one new object and add frames. After video is finished, I delete the object but GStreamer internal memory looks like is not released.
After several videos generated all RAM is allocated and linux kills the process.
Why is this happening? How can I solve this issue? Is there any other way to do it?
GVideo.h:
#ifndef GVIDEO_H
#define GVIDEO_H
#include <gst/gst.h>
#include <string>
class GVideo
{
public:
GVideo();
~GVideo();
void startVideo(std::string filename);
void endVideo();
void addFrame(GstSample* element);
bool isRecording(){return _isRecording;}
bool isDataNeed(){return _dataNeed;}
private:
void setDataNeed(bool dataNeed){_dataNeed = dataNeed;}
protected:
bool _isRecording;
bool _dataNeed;
int _frameRate;
int _duration;
GstClockTime _timestamp;
GstElement *_pipeline;
GstElement *_source;
};
#endif //GVIDEO_H
GVideo.cpp
#include "GVideo.h"
#include <gst/app/gstappsrc.h>
#include <iostream>
static gboolean bus_video_call(GstBus* bus, GstMessage* msg, void* user_data)
{
//std::cout << "BUS_CALL" << std::endl;
GVideo* video = (GVideo*)user_data;
switch (GST_MESSAGE_TYPE(msg))
{
case GST_MESSAGE_EOS:
{
std::cout << "VIDEO GST_MESSAGE_EOS" << std::endl;
video->endVideo();
break;
}
case GST_MESSAGE_ERROR:
{
std::cout << "GST_MESSAGE_ERROR" << std::endl;
GError *err;
gst_message_parse_error(msg, &err, NULL);
g_error("%s", err->message);
g_error_free(err);
video->endVideo();
break;
}
default:
break;
}
return true;
}
GVideo::GVideo()
: _dataNeed(false), _isRecording(false)
{
_pipeline = NULL;
_source = NULL;
}
GVideo::~GVideo()
{
std::cout << "Deleting GstVideo." << std::endl;
if(_pipeline != NULL)
endVideo();
}
void GVideo::startVideo(std::string filename)
{
_isRecording = true;
_frameRate = 2;
_duration = 5;
_timestamp = 0;
_dataNeed = true;
_pipeline = gst_pipeline_new ("video_pipeline");
_source = gst_element_factory_make ("appsrc" , "video_source");
GstElement* _videorate = gst_element_factory_make ("videorate" , "video_vidrate");
GstElement* _capsfilter = gst_element_factory_make ("capsfilter" , "video_capsfilter");
GstElement* _videoconvert = gst_element_factory_make ("videoconvert", "video_conv");
GstElement* _encoder = gst_element_factory_make ("x264enc" , "video_enc");
GstElement* _muxer = gst_element_factory_make ("mp4mux" , "video_mux");
GstElement* _filesink = gst_element_factory_make ("filesink" , "video_filesink");
// g_object_set (G_OBJECT (_source), "num-buffers", _duration*_frameRate, NULL);
g_object_set (G_OBJECT (_source), "caps",
gst_caps_new_simple ( "video/x-raw",
"format", G_TYPE_STRING, "I420",
"width", G_TYPE_INT, 352,
"height", G_TYPE_INT, 288,
"framerate", GST_TYPE_FRACTION, _frameRate, 1,
NULL), NULL);
/* setup appsrc */
g_object_set (G_OBJECT (_source),
// "do-timestamp", TRUE,
"stream-type", GST_APP_STREAM_TYPE_STREAM,
"format", GST_FORMAT_TIME, NULL);
g_object_set (G_OBJECT (_capsfilter), "caps",
gst_caps_new_simple ("video/x-raw",
// "format", G_TYPE_STRING, "I420",
"framerate", GST_TYPE_FRACTION, 30, 1,
NULL), NULL);
gst_bin_add_many (GST_BIN (_pipeline), _source, _videorate, _capsfilter, _videoconvert, _encoder, _muxer, _filesink, NULL);
gst_element_link_many (_source, _videorate, _capsfilter, _videoconvert, _encoder, _muxer, _filesink, NULL);
g_object_set (G_OBJECT (_filesink), "location", filename.c_str(), NULL);
GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(_pipeline));
gst_bus_add_watch(bus, bus_video_call, this);
gst_object_unref(bus);
gst_element_set_state (_pipeline, GST_STATE_PLAYING);
}
void GVideo::addFrame(GstSample* element)
{
GstBuffer* buf = gst_sample_get_buffer((GstSample*)element);
GST_BUFFER_PTS (buf) = _timestamp;
GST_BUFFER_DURATION (buf) = gst_util_uint64_scale_int (1, GST_SECOND, _frameRate);
_timestamp += GST_BUFFER_DURATION (buf);
gst_app_src_push_sample(GST_APP_SRC(_source), element);
if(_timestamp >= _duration*GST_SECOND)
{
_dataNeed = false;
gst_app_src_end_of_stream(GST_APP_SRC(_source));
}
}
void GVideo::endVideo()
{
std::cout << "gst_element_set_state." << std::endl;
gst_element_set_state (_pipeline, GST_STATE_NULL);
std::cout << "gst_object_unref." << std::endl;
gst_object_unref(_pipeline);
std::cout << "_pipeline= NULL." << std::endl;
_pipeline = NULL;
std::cout << "setDataNeed." << std::endl;
_isRecording = false;
}
I would guess that whatever is calling addFrame() might need to unref the sample.
https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-libs/html/gst-plugins-base-libs-appsrc.html#gst-app-src-push-sample
In the docs there it indicates "transfer: none" on the sample parameter, which I believe means the caller needs to unref. For some reason the older method gst_app_src_push_buffer has transfer "full". Dunno why.
https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-libs/html/gst-plugins-base-libs-appsrc.html#gst-app-src-push-buffer
I am having one heck of a time reaching my end goal with this one and really hope someone can help me out. I am very new to gstreamer and have been working on this issue for several days. The end goal of everything is to have a piece of software that will take video from my Point Grey Blackfly USB 3 camera and compress it then send it out over the UDPSink through gstreamer. As of now I am attempting to simply show that this is possible by getting the video from the camera to display via gstreamer. In other words, I am trying to do something like this
Appsrc(camera)->ffmpegcolorspace->xvimagesink
just to get some sort of result...
My setup is as follows:
Nvidia Jetson TK1,
Ubunutu 14.04,
Gstreamer0.10,
OpenCV(installed but not currently used, have been considering throwing that in too)
Below is the code I currently have, it does not work but it will compile and run.
#include <stdio.h>
#include <gst/gst.h>
#include <gst/app/gstappsrc.h>
#include <gst/video/video.h>
#include "FlyCapture2.h"
#include <iostream>
#include <sstream>
using namespace FlyCapture2;
using namespace std;
typedef struct {
GstPipeline *pipeline;
GstAppSrc *src;
GstElement *sink;
GstElement *encoder;
GstElement *decoder;
GstElement *ffmpeg;
GstElement *xvimagesink;
GMainLoop *loop;
guint sourceid;
FILE *file;
}gst_app_t;
static gst_app_t gst_app;
Camera camera;
#define BUFF_SIZE (1024)
void getImagePtr(guint8 * &ptr, gint &size);
static gboolean read_data(gst_app_t *app)
{
GstBuffer *buffer;
guint8 *ptr;
gint size;
GstFlowReturn ret;
getImagePtr(ptr, size);
//cout << size << endl;
// size = fread(ptr, 1, BUFF_SIZE, app->file);
if(size == 0){
ret = gst_app_src_end_of_stream(app->src);
g_debug("eos returned %d at %d\n", ret, __LINE__);
return FALSE;
}
GstCaps *caps = NULL;
std::stringstream video_caps_text;
video_caps_text << "video/x-raw-rgb,bpp=(int)24,depth=(int)24,endianness=(int)4321,red_mask=(int)16711680,green_mask=(int)65280,blue_mask=(int)255,width=(int)1288,height=(int)964,framerate=(fraction)0/1";
caps = gst_caps_from_string( video_caps_text.str().c_str() );
g_object_set( G_OBJECT(app->src), "caps", caps, NULL);
buffer = gst_buffer_new();
GST_BUFFER_MALLOCDATA(buffer) = ptr;
GST_BUFFER_SIZE(buffer) = size;
GST_BUFFER_DATA(buffer) = GST_BUFFER_MALLOCDATA(buffer);
{
GstCaps *caps_source = NULL;
std::stringstream video_caps_text;
video_caps_text << "video/x-raw-rgb,bpp=(int)24,depth=(int)24,endianness=(int)4321,red_mask=(int)16711680,green_mask=(int)65280,blue_mask=(int)255,width=(int)1288,height=(int)964,framerate=(fraction)0/1";
caps_source = gst_caps_from_string( video_caps_text.str().c_str() );
cout<<video_caps_text.str()<<endl;
if( !GST_IS_CAPS( caps_source) ){
cout<<"ERROR MAKING CAPS"<<endl;
exit(1);
}
gst_app_src_set_caps( GST_APP_SRC( app->src ), caps_source);
gst_buffer_set_caps( buffer, caps_source);
gst_caps_unref( caps_source );
}
ret = gst_app_src_push_buffer(app->src, buffer);
if(ret != GST_FLOW_OK){
g_debug("push buffer returned %d for %d bytes \n", ret, size);
return FALSE;
}
else if(ret == GST_FLOW_OK){
//cout<<"FLOW OK"<<endl;
}
if(!(size > BUFF_SIZE)){
cout<<"ISSUE FOUND"<<endl;
ret = gst_app_src_end_of_stream(app->src);
g_debug("eos returned %d at %d\n", ret, __LINE__);
return FALSE;
}
return TRUE;
}
static void start_feed (GstElement * pipeline, guint size, gst_app_t *app)
{
if (app->sourceid == 0) {
GST_DEBUG ("start feeding");
app->sourceid = g_idle_add ((GSourceFunc) read_data, app);
}
}
static void stop_feed (GstElement * pipeline, gst_app_t *app)
{
if (app->sourceid != 0) {
GST_DEBUG ("stop feeding");
g_source_remove (app->sourceid);
app->sourceid = 0;
}
}
static void on_pad_added(GstElement *element, GstPad *pad)
{
cout<<"PAD ADDED"<<endl;
GstCaps *caps;
GstStructure *str;
gchar *name;
GstPad *ffmpegsink;
GstPadLinkReturn ret;
g_debug("pad added");
caps = gst_pad_get_caps(pad);
str = gst_caps_get_structure(caps, 0);
cout<<"CAPS: "<<str<<endl;
g_assert(str);
name = (gchar*)gst_structure_get_name(str);
cout<<"NAME IS: "<<name<<endl;
g_debug("pad name %s", name);
if(g_strrstr(name, "video")){
ffmpegsink = gst_element_get_pad(gst_app.ffmpeg, "sink");
g_assert(ffmpegsink);
ret = gst_pad_link(pad, ffmpegsink);
g_debug("pad_link returned %d\n", ret);
gst_object_unref(ffmpegsink);
}
gst_caps_unref(caps);
}
static gboolean bus_callback(GstBus *bus, GstMessage *message, gpointer *ptr)
{
gst_app_t *app = (gst_app_t*)ptr;
switch(GST_MESSAGE_TYPE(message)){
case GST_MESSAGE_ERROR:{
gchar *debug;
GError *err;
gst_message_parse_error(message, &err, &debug);
g_print("Error %s\n", err->message);
g_error_free(err);
g_free(debug);
g_main_loop_quit(app->loop);
}
break;
case GST_MESSAGE_WARNING:{
gchar *debug;
GError *err;
const gchar *name;
gst_message_parse_warning(message, &err, &debug);
g_print("Warning %s\nDebug %s\n", err->message, debug);
name = GST_MESSAGE_SRC_NAME(message);
g_print("Name of src %s\n", name ? name : "nil");
g_error_free(err);
g_free(debug);
}
break;
case GST_MESSAGE_EOS:
g_print("End of stream\n");
g_main_loop_quit(app->loop);
break;
case GST_MESSAGE_STATE_CHANGED:
break;
default:
g_print("got message %s\n", \
gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
break;
}
return TRUE;
}
void PrintError( Error error )
{
error.PrintErrorTrace();
}
void connectCamera(){
cout<<"STARTING CONNECTION FUNCTION"<<endl;
Error error;
BusManager busMgr;
unsigned int numCameras;
PGRGuid guid;
error = busMgr.GetNumOfCameras(&numCameras);
if (error != PGRERROR_OK)
{
PrintError (error);
}
cout << "Number of cameras detected: " << numCameras << endl;
for (unsigned int i=0; i < numCameras; i++)
{
error = busMgr.GetCameraFromIndex(i, &guid);
if (error != PGRERROR_OK)
{
PrintError( error );
}
}
// Connect the camera
error = camera.Connect( &guid );
if ( error != PGRERROR_OK )
{
std::cout << "Failed to connect to camera" << std::endl;
return;
}
else
std::cout << "CONNECTED!" << std::endl;
}
void getImagePtr( guint8 * &ptr, gint &size){
// Get the image
Image rawImage;
Error error = camera.RetrieveBuffer( &rawImage );
if ( error != PGRERROR_OK )
{
std::cout << "capture error" << std::endl;
}
// convert to rgb
Image bgrImage;
rawImage.Convert( FlyCapture2::PIXEL_FORMAT_BGR, &bgrImage );
// cout << rawImage.GetDataSize() << endl;
ptr = (guint8*)g_malloc(bgrImage.GetDataSize());
g_assert(ptr);
memcpy( ptr,bgrImage.GetData(), bgrImage.GetDataSize() );
size = bgrImage.GetDataSize();
// ptr = bgrImage.GetData();
}
int main(int argc, char *argv[])
{
gst_app_t *app = &gst_app;
GstBus *bus;
GstStateChangeReturn state_ret;
if(argc != 2){
printf("File name not specified\n");
return 1;
}
connectCamera();
camera.StartCapture();
app->file = fopen(argv[1], "r");
g_assert(app->file);
gst_init(NULL, NULL);
app->pipeline = (GstPipeline*)gst_pipeline_new("mypipeline");
bus = gst_pipeline_get_bus(app->pipeline);
gst_bus_add_watch(bus, (GstBusFunc)bus_callback, app);
gst_object_unref(bus);
app->src = (GstAppSrc*)gst_element_factory_make("appsrc", "mysrc");
//app->encoder = gst_element_factory_make("nv_omx_h264enc", "nvidEnc");
//app->decoder = gst_element_factory_make("decodebin", "mydecoder");
app->ffmpeg = gst_element_factory_make("ffmpegcolorspace", "myffmpeg");
app->xvimagesink = gst_element_factory_make("xvimagesink", "myvsink");
g_assert(app->src);
//g_assert(app->encoder);
//g_assert(app->decoder);
g_assert(app->ffmpeg);
g_assert(app->xvimagesink);
g_signal_connect(app->src, "need-data", G_CALLBACK(start_feed), app);
g_signal_connect(app->src, "enough-data", G_CALLBACK(stop_feed), app);
//g_signal_connect(app->decoder, "pad-added",
// G_CALLBACK(on_pad_added), app->decoder);
//gst_bin_add_many(GST_BIN(app->pipeline), (GstElement*)app->src, app->encoder,
//app->decoder, app->ffmpeg, app->xvimagesink, NULL);
gst_bin_add_many(GST_BIN(app->pipeline), (GstElement*)app->src, app->ffmpeg, app->xvimagesink, NULL);
//if(!gst_element_link((GstElement*)app->src, app->encoder)){
//g_warning("failed to link src anbd decoder");
//}
//if(!gst_element_link(app->encoder, app->decoder)){
// g_warning("failed to link encoder and decoder");
//}
if(!gst_element_link(app->ffmpeg, app->xvimagesink)){
g_warning("failed to link ffmpeg and xvsink");
}
state_ret = gst_element_set_state((GstElement*)app->pipeline, GST_STATE_PLAYING);
g_warning("set state returned %d\n", state_ret);
app->loop = g_main_loop_new(NULL, FALSE);
//GstCaps *appsrcCaps = NULL;
//appsrcCaps = gst_video_format_new_caps(GST_VIDEO_FORMAT_BGR, 1288, 964, 0, 1, 4, 3);
//gst_app_src_set_caps(GST_APP_SRC(app->src), appsrcCaps);
g_main_loop_run(app->loop);
camera.StopCapture();
camera.Disconnect();
state_ret = gst_element_set_state((GstElement*)app->pipeline, GST_STATE_NULL);
g_warning("set state null returned %d\n", state_ret);
return 0;
}
I keep getting an Internal data flow error on every run and I am not sure from what. I think one of my issues may be the "caps" but like I said, I am very new to this.
Any help will be greatly appreciated. Let me know if there is anything else I can add to be more clear. Thank you so much!
For the first look (I haven't gone into details) seems you link ffmpeg with xvimagesink only, try to link your src element also:
gst_element_link(app->src, app->ffmpeg);
You can read about linking elements more here: http://gstreamer.freedesktop.org/data/doc/gstreamer/head/manual/html/section-elements-link.html