在这篇文章我写到了纯音频ogg文件的播放方法:
GStreamer学习笔记 – GStreamer实现ogg 格式音频播放及代码解析
但ogg本质是一个容器,可以同时容纳音频和视频,接下来讲两者如何同时播放。
音频和视频属于不同的数据流,解码转流和输出属于不同线路,所以需要至少两个线程来完成,GStreamer通过queue element来完成线程的创建开始和结束,其创建通过如下调用完成:
1 2 3 4 |
GstElement *vdoQueue = gst_element_factory_make ("queue", "video-queue"); GstElement *audioQueue = gst_element_factory_make ("queue", "audio-queue"); |
在需要开启线程的点之前加入queue element即可,然后再按实际情况链接各个元素。
为了让程序更加具有拓展性,需要获取ogg文件包含的数据流,根据数据流类型来决定是否需要两个线程来完成音频或视频的播放。如果不进行判断,直接加入两个queue element,则会出现一条不完整的链接,导致程序异常。
因为queue element需要等待pad added事件到来,再链接到demuxer的pad,如果只有一种数据流,pad added只会发生一次,也只能链接一个类型的queue element,剩下的则没有可链接的了。
至于为什么其中一条链接不完整会导致其他链接也不能正常工作,不知道它内部原理,所以就无法得知了。
一:获取ogg文件内部包含哪些编码格式
ogg视频的编码格式为theora,音频的为vorbis,这里采用简单粗暴的办法,直接从ogg源文件的开头100多个字节中提取出字符串,如果里面包含theora,则含有视频流,包含vorbis则包含音频流,它的编码格式中应该是有说明需要在首部声明使用的编码方式的,用vim打开ogg文件即可以看到theora或者vorbis的字符串。
实现代码如下:
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 |
void get_stream_type(char **result, char *file) { char buf[110]; FILE *fp = fopen(file, "r"); if ( !fp ) { fprintf(stderr, "Open file failed\n"); exit(1); } if ( fread (buf, sizeof(char), sizeof(buf), fp) < 0) { fprintf (stderr, "fread failed\n"); exit(1); } /*extract vorbis or theora format name if it's exist * and record its index*/ int i1 = 0, i2 = 0; for ( int i=0; i<110; i++ ) { if (buf[i]=='t' || buf[i]=='v') { if ( !i1 ) i1 = i; else i2 = i; } } buf[i1+6] = buf[i2+6] = '\0'; strcpy(result[0], &buf[i1]); strcpy(result[1], &buf[i2]); result[0][6] = result[1][6] = '\0'; printf("%s\n", result[0]); printf("%s\n", result[1]); fclose (fp); } |
二:根据包含的流进行相应元素创建和连接
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 |
#include <gst/gst.h> #include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct Queue { GstElement *queue[2]; } Queue; void get_stream_type (char **result, char *file); static gboolean bus_call (GstBus *bus, GstMessage *msg, gpointer data); static void on_pad_added (GstElement *element, GstPad *pad, gpointer data); int main (int argc, char *argv[]) { GMainLoop *loop; GstElement *pipeline, *source, *demuxer, *decoder, *conv, *sink; GstBus *bus; guint bus_watch_id; /* Check input arguments */ if (argc != 2) { g_printerr ("Usage: %s <Ogg/Vorbis filename>\n", argv[0]); return -1; } /* Initialisation */ gst_init (&argc, &argv); loop = g_main_loop_new (NULL, FALSE); /* Create gstreamer elements */ pipeline = gst_pipeline_new ("audio-player"); source = gst_element_factory_make ("filesrc", "file-source"); demuxer = gst_element_factory_make ("oggdemux", "ogg-demuxer"); /*视频解析转流输出元素*/ decoder = gst_element_factory_make ("theoradec", "vorbis-decoder"); conv = gst_element_factory_make ("videoconvert", "converter"); sink = gst_element_factory_make ("autovideosink", "video-output"); /*音频解析转流输出元素*/ GstElement *decoder_audio = gst_element_factory_make ("vorbisdec", "audio-decoder"); GstElement *converter_audio = gst_element_factory_make ("audioconvert", "audio_converter"); GstElement *sink_audio = gst_element_factory_make ("autoaudiosink", "audio-ouput"); if (!pipeline || !source || !demuxer || !decoder || !conv || !sink ) { g_printerr ("One element could not be created. Exiting.\n"); return -1; } /* Set up the pipeline */ g_object_set (G_OBJECT (source), "location", argv[1], NULL); GstElement *vdoQueue = NULL; GstElement *audioQueue = NULL; char format1[10] = { 0 }; char format2[10] = { 0 }; char *result[2]; result[0] = format1; result[1] = format2; /*获取包含的流编码格式*/ get_stream_type(result, argv[1]); /*如果包含theora编码格式,则含有视频流,进行相关元素的添加和链接*/ if (strcmp (format1, "theora") == 0 || strcmp (format2, "theora") == 0) { vdoQueue = gst_element_factory_make ("queue", "video-queue"); gst_bin_add_many (GST_BIN(pipeline), vdoQueue, decoder, conv, sink , NULL); gst_element_link_many (vdoQueue, decoder, conv, sink, NULL); } if (strcmp (format1, "vorbis") == 0 || strcmp (format2, "vorbis") == 0) { audioQueue = gst_element_factory_make ("queue", "audio-queue"); gst_bin_add_many (GST_BIN(pipeline), audioQueue, decoder_audio, converter_audio, sink_audio, NULL); gst_element_link_many (audioQueue, decoder_audio, converter_audio, sink_audio, NULL); } /* we add a message handler */ bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); bus_watch_id = gst_bus_add_watch (bus, bus_call, loop); gst_object_unref (bus); gst_bin_add_many (GST_BIN (pipeline), source, demuxer,NULL); if ( gst_element_link (source, demuxer) != TRUE ){ g_print("Elements couldn't be linked\n"); return 1; } Queue queue; queue.queue[0] = audioQueue; queue.queue[1] = vdoQueue; g_signal_connect (demuxer, "pad-added", G_CALLBACK (on_pad_added), (void*)&queue); /* note that the demuxer will be linked to the decoder dynamically. The reason is that Ogg may contain various streams (for example audio and video). The source pad(s) will be created at run time, by the demuxer when it detects the amount and nature of streams. Therefore we connect a callback function which will be executed when the "pad-added" is emitted.*/ /* Set the pipeline to "playing" state*/ g_print ("Now playing: %s\n", argv[1]); gst_element_set_state (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); return 0; } static void on_pad_added (GstElement *element, GstPad *pad, gpointer data) { GstPad *sinkpad_audio, *sinkpad_vdo; Queue *queue = (Queue *) data; /* We can now link this pad with the vorbis-decoder sink pad */ g_print ("Dynamic pad created, linking demuxer/decoder\n"); /*queue元素值不为空则进行pad的连接*/ if ( queue->queue[0] ) { sinkpad_audio = gst_element_get_static_pad (queue->queue[0], "sink"); GstPad *pad1 = gst_element_get_compatible_pad (element, sinkpad_audio, NULL); if ( pad1 ) { gst_pad_link(pad1, sinkpad_audio); gst_object_unref (GST_OBJECT (pad1)); g_print("link audio\n"); } gst_object_unref (sinkpad_audio); } if ( queue->queue[1] ) { sinkpad_vdo = gst_element_get_static_pad (queue->queue[1], "sink"); GstPad *pad2 = gst_element_get_compatible_pad (element, sinkpad_vdo, NULL); if ( pad2 ) { gst_pad_link(pad2, sinkpad_vdo); gst_object_unref (GST_OBJECT (pad2)); g_print("link video\n"); } gst_object_unref (sinkpad_vdo); } } void get_stream_type(char **result, char *file) { char buf[110]; FILE *fp = fopen(file, "r"); if ( !fp ) { fprintf(stderr, "Open file failed\n"); exit(1); } if ( fread (buf, sizeof(char), sizeof(buf), fp) < 0) { fprintf (stderr, "fread failed\n"); exit(1); } /*extract vorbis or theora format name if it's exist * and record its index*/ int i1 = 0, i2 = 0; for ( int i=0; i<110; i++ ) { if (buf[i]=='t' || buf[i]=='v') { if ( !i1 ) i1 = i; else i2 = i; } } buf[i1+6] = buf[i2+6] = '\0'; strcpy(result[0], &buf[i1]); strcpy(result[1], &buf[i2]); result[0][6] = result[1][6] = '\0'; printf("%s\n", result[0]); printf("%s\n", result[1]); fclose (fp); } 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; } |
将上诉代码保存到 vdoAdoOGGPlayer.c
编译:
1 2 3 |
gcc `pkg-config --cflags --libs gstreamer-1.0` vdoAdoOGGPlayer.c -g -o vdoAdoOGGPlayer |
运行:
1 2 3 |
./vdoAdoOGGPlayer ~/Downloads/1.ogg //注意修改ogg文件为自己的 |
其中ogg文件可以是纯音频,纯视频,或者杂糅了音频和视频。