• C++ gstreamer函数使用总结


    1、GSteamer的基本API的使用

    gst_init()初始化GStreamer 。

    gst_parse_launch()从文本描述快速构建管道 。

    playbin创建自动播放管道。

    gst_element_set_state()通知GStreamer开始播放 。

    gst_element_get_bus()和gst_bus_timed_pop_filtered()来释放资源     

    1. #include <iostream>
    2. #include <gst/gst.h>
    3. #include <glib.h>
    4. int main(int argc, char *argv[]) {
    5. GstElement *pipeline;
    6. GstBus *bus;
    7. GstMessage *msg;
    8. /* Initialize GStreamer */
    9. gst_init(&argc, &argv);
    10. //初始化gstream
    11. /* Build the pipeline */
    12. pipeline =gst_parse_launch("playbin uri=file:///D:/gstream/1.mp4",NULL);
    13. //gst_parse_launch使用系统预设的管道来处理流媒体。gst_parse_launch创建的是一个由playbin单元素组成的管道
    14. /* Start playing */
    15. gst_element_set_state(pipeline, GST_STATE_PLAYING);
    16. //将我们的元素设置为playing状态才能开始播放
    17. /* Wait until error or EOS */
    18. bus = gst_element_get_bus(pipeline);
    19. msg =gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE,GST_MESSAGE_ERROR ); //| GST_MESSAGE_EOS
    20. //遇到错误或者播放完毕以后gst_bus_timed_pop_filtered()会返回一条消息
    21. /* Free resources */
    22. if (msg != NULL) {
    23. gst_message_unref(msg);
    24. //需要使用gst_message_unref()将msg释放,此函数专门清除gst_bus_timed_pop_filtered
    25. gst_object_unref(bus);
    26. gst_element_set_state(pipeline, GST_STATE_NULL);
    27. gst_object_unref(pipeline);
    28. }
    29. printf("finish");
    30. return 0;
    31. }

    2、创建元件并且链接起来

    1. #include <gst/gst.h>
    2. int main(int argc, char *argv[]) {
    3. GstElement *pipeline, *source, *sink;
    4. GstBus *bus;
    5. GstMessage *msg;
    6. GstStateChangeReturn ret;
    7. /* Initialize GStreamer */
    8. gst_init(&argc, &argv);
    9. /* Create the elements */
    10. source = gst_element_factory_make("videotestsrc", "source");
    11. sink = gst_element_factory_make("autovideosink", "sink");
    12. //创建元件,参数:元件的类型,元件名称
    13. //videotestsrc是一个源元素(它产生数据),它创建一个测试视频模式。
    14. //autovideosink是一个接收器元素(它消耗数据),它在窗口上显示它接收到的图像。
    15. /* Create the empty pipeline */
    16. pipeline = gst_pipeline_new("test-pipeline");
    17. //创建管道,管道是一种特殊类型的bin,(估计就是箱柜)
    18. if (!pipeline || !source || !sink) {
    19. g_printerr("Not all elements could be created.
    20. ");
    21. return -1;
    22. }
    23. /* Build the pipeline */
    24. gst_bin_add_many(GST_BIN(pipeline), source, sink, NULL);
    25. //向管道中添加元件,以null结尾,添加单个和可以,函数是:gst_bin_add()
    26. if (gst_element_link(source, sink) != TRUE) {
    27. g_printerr("Elements could not be linked.
    28. ");
    29. gst_object_unref(pipeline);
    30. return -1;
    31. }
    32. /* Modify the source's properties */
    33. g_object_set(source, "pattern", 0, NULL);
    34. //修改元件的属性
    35. /* Start playing */
    36. ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);
    37. if (ret == GST_STATE_CHANGE_FAILURE) {
    38. g_printerr("Unable to set the pipeline to the playing state.
    39. ");
    40. gst_object_unref(pipeline);
    41. return -1;
    42. }
    43. //设置管道开始工作
    44. //调用gst_element_set_state(),并且检查其返回值是否有错误。
    45. /* Wait until error or EOS */
    46. bus = gst_element_get_bus(pipeline);
    47. //获取pipeline的总线
    48. msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR );
    49. //gst_bus_timed_pop_filtered()等待执行结束并返回GstMessage
    50. /* Parse message:
    51. GstMessage是一种非常通用的结构,
    52. 通过使用 GST_MESSAGE_TYPE()宏可以获得其中的消息
    53. */
    54. if (msg != NULL) {
    55. GError *err;
    56. gchar *debug_info;
    57. switch (GST_MESSAGE_TYPE(msg)) {
    58. case GST_MESSAGE_ERROR:
    59. gst_message_parse_error(msg, &err, &debug_info);
    60. g_printerr("Error received from element %s: %s
    61. ", GST_OBJECT_NAME(msg->src), err->message);
    62. g_printerr("Debugging information: %s
    63. ", debug_info ? debug_info : "none");
    64. g_clear_error(&err);
    65. g_free(debug_info);
    66. break;
    67. case GST_MESSAGE_EOS:
    68. g_print("End-Of-Stream reached.
    69. ");
    70. break;
    71. default:
    72. /* We should not reach here because we only asked for ERRORs and EOS */
    73. g_printerr("Unexpected message received.
    74. ");
    75. break;
    76. }
    77. gst_message_unref(msg);
    78. }
    79. /* Free resources */
    80. gst_object_unref(bus);
    81. gst_element_set_state(pipeline, GST_STATE_NULL);
    82. gst_object_unref(pipeline);
    83. return 0;
    84. }

    3、添加衬垫,添加回调,手动链接衬垫

    1. #include <gst/gst.h>
    2. /* Structure to contain all our information, so we can pass it to callbacks */
    3. typedef struct _CustomData {
    4. GstElement *pipeline;
    5. GstElement *source;
    6. GstElement *convert;
    7. GstElement *resample;
    8. GstElement *sink;
    9. } CustomData;
    10. //先建立一个结构,里面放了一个pipeline指针和四个元件指针
    11. /* Handler for the pad-added signal */
    12. static void pad_added_handler(GstElement *src, GstPad *pad, CustomData *data);
    13. //声明一个函数,叫添加衬垫的函数pad_added_handler。
    14. int main(int argc, char *argv[]) {
    15. CustomData data;
    16. GstBus *bus;
    17. GstMessage *msg;
    18. GstStateChangeReturn ret;
    19. gboolean terminate = FALSE;
    20. /* Initialize GStreamer */
    21. gst_init(&argc, &argv);
    22. //同样需要先初始化
    23. /* Create the elements */
    24. data.source = gst_element_factory_make("uridecodebin", "source");
    25. data.convert = gst_element_factory_make("audioconvert", "convert");
    26. data.resample = gst_element_factory_make("audioresample", "resample");
    27. data.sink = gst_element_factory_make("autoaudiosink", "sink");
    28. /* Create the empty pipeline */
    29. data.pipeline = gst_pipeline_new("test-pipeline");
    30. //先把data里的信息创建出来,创建了一个pipeline和四个元件
    31. if (!data.pipeline || !data.source || !data.convert || !data.resample || !data.sink) {
    32. g_printerr("Not all elements could be created.
    33. ");
    34. return -1;
    35. }
    36. /* Build the pipeline. Note that we are NOT linking the source at this
    37. * point. We will do it later. */
    38. gst_bin_add_many(GST_BIN(data.pipeline), data.source, data.convert, data.resample, data.sink, NULL);
    39. if (!gst_element_link_many(data.convert, data.resample, data.sink, NULL)) {
    40. g_printerr("Elements could not be linked.
    41. ");
    42. gst_object_unref(data.pipeline);
    43. return -1;
    44. }
    45. /* Set the URI to play */
    46. g_object_set(data.source, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);
    47. //大约是将source元件的衬垫链接到某个网址上
    48. /* Connect to the pad-added signal */
    49. g_signal_connect(data.source, "pad-added", G_CALLBACK(pad_added_handler), &data);
    50. //GSignals是GStreamer中的关键点。它们使您可以在发生事情时(通过回调)得到通知,所以我们为source元件添加了一个回调
    51. //这个回调好像没有传递参数啊喂,好吧,官方是真么说的:src是GstElement触发信号的。在此示例中,它只能是uridecodebin。newpad是刚刚添加到src元素中的,我理解为哦我们为source添加回调这件事就是增加了一个衬垫,data是当作指针来传递信号的
    52. /* Start playing */
    53. ret = gst_element_set_state(data.pipeline, GST_STATE_PLAYING);
    54. if (ret == GST_STATE_CHANGE_FAILURE) {
    55. g_printerr("Unable to set the pipeline to the playing state.
    56. ");
    57. gst_object_unref(data.pipeline);
    58. return -1;
    59. }
    60. /* Listen to the bus */
    61. bus = gst_element_get_bus(data.pipeline);
    62. do {
    63. msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ANY);
    64. //等待执行结束并且返回
    65. //顺带说一句,以前的老语法是GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS这样的,所以下文中的case用的是这几个错误信息,但是现在这个语法不被支持了。嗯嗯
    66. /* Parse message */
    67. if (msg != NULL) {
    68. GError *err;
    69. gchar *debug_info;
    70. switch (GST_MESSAGE_TYPE(msg)) {
    71. case GST_MESSAGE_ERROR:
    72. gst_message_parse_error(msg, &err, &debug_info);
    73. g_printerr("Error received from element %s: %s
    74. ", GST_OBJECT_NAME(msg->src), err->message);
    75. g_printerr("Debugging information: %s
    76. ", debug_info ? debug_info : "none");
    77. g_clear_error(&err);
    78. g_free(debug_info);
    79. terminate = TRUE;
    80. break;
    81. case GST_MESSAGE_EOS:
    82. g_print("End-Of-Stream reached.
    83. ");
    84. terminate = TRUE;
    85. break;
    86. case GST_MESSAGE_STATE_CHANGED:
    87. /* We are only interested in state-changed messages from the pipeline */
    88. if (GST_MESSAGE_SRC(msg) == GST_OBJECT(data.pipeline)) {
    89. GstState old_state, new_state, pending_state;
    90. gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state);
    91. g_print("Pipeline state changed from %s to %s:
    92. ",
    93. gst_element_state_get_name(old_state), gst_element_state_get_name(new_state));
    94. }
    95. break;
    96. default:
    97. /* We should not reach here */
    98. g_printerr("Unexpected message received.
    99. ");
    100. break;
    101. }
    102. gst_message_unref(msg);
    103. }
    104. } while (!terminate);
    105. //只要不中止,就一直监视执行结束的状态
    106. /* Free resources */
    107. gst_object_unref(bus);
    108. gst_element_set_state(data.pipeline, GST_STATE_NULL);
    109. gst_object_unref(data.pipeline);
    110. return 0;
    111. }
    112. /* This function will be called by the pad-added signal */
    113. static void pad_added_handler(GstElement *src, GstPad *new_pad, CustomData *data) {
    114. GstPad *sink_pad = gst_element_get_static_pad(data->convert, "sink");
    115. //pipeline的链接顺序是:source-convert-resample-sink,我们为source添加了回调,然后此处在回调内部获取了convert的对应的衬垫
    116. GstPadLinkReturn ret;
    117. GstCaps *new_pad_caps = NULL;
    118. GstStructure *new_pad_struct = NULL;
    119. const gchar *new_pad_type = NULL;
    120. g_print("Received new pad '%s' from '%s':
    121. ", GST_PAD_NAME(new_pad), GST_ELEMENT_NAME(src));
    122. /* If our converter is already linked, we have nothing to do here */
    123. if (gst_pad_is_linked(sink_pad)) {
    124. g_print("We are already linked. Ignoring.
    125. ");
    126. goto exit;
    127. }
    128. //此处应该是检查新为source添加的衬垫是不是已经链接到了convert衬垫
    129. /* Check the new pad's type */
    130. new_pad_caps = gst_pad_get_current_caps(new_pad);
    131. new_pad_struct = gst_caps_get_structure(new_pad_caps, 0);
    132. new_pad_type = gst_structure_get_name(new_pad_struct);
    133. if (!g_str_has_prefix(new_pad_type, "audio/x-raw")) {
    134. g_print("It has type '%s' which is not raw audio. Ignoring.
    135. ", new_pad_type);
    136. goto exit;
    137. }
    138. //检查这个衬垫当前输出的数据类型,经过一番解析,如果发现里面没有"audio/x-raw",那说明这不是解码音频的
    139. /* Attempt the link */
    140. ret = gst_pad_link(new_pad, sink_pad);
    141. if (GST_PAD_LINK_FAILED(ret)) {
    142. g_print("Type is '%s' but link failed.
    143. ", new_pad_type);
    144. }
    145. else {
    146. g_print("Link succeeded (type '%s').
    147. ", new_pad_type);
    148. }
    149. //如果两个衬垫没链接,那就人为地链接起来
    150. exit:
    151. //这个语法就厉害了,首先定义了一个exit标号,如果前文中goto exit;那转到的就将会是此处
    152. /* Unreference the new pad's caps, if we got them */
    153. if (new_pad_caps != NULL)
    154. gst_caps_unref(new_pad_caps);
    155. /* Unreference the sink pad */
    156. gst_object_unref(sink_pad);
    157. }

    4、打印gstreamer的版本信息

    1. #include <iostream>
    2. #include <gst/gst.h>
    3. #include <glib.h>
    4. //#include <gst/gst.h>
    5. int main(int argc,char *argv[])
    6. {
    7. const gchar *nano_str;
    8. guint major, minor, micro, nano;
    9. gst_init(&argc, &argv);
    10. gst_version(&major, &minor, &micro, &nano);
    11. if (nano == 1)
    12. nano_str = "(CVS)";
    13. else if (nano == 2)
    14. nano_str = "(Prerelease)";
    15. else
    16. nano_str = "";
    17. printf("This program is linked against GStreamer %d.%d.%d %s
    18. ",major, minor, micro, nano_str);
    19. return 0;
    20. }

    5、gstreamer封装的argparse

    1. #include <iostream>
    2. #include <gst/gst.h>
    3. #include <glib.h>
    4. #include <gst/gst.h>
    5. int main(int argc,char *argv[])
    6. {
    7. gboolean silent = FALSE;
    8. gchar *savefile = NULL;
    9. GOptionContext *ctx;
    10. GError *err = NULL;
    11. GOptionEntry entries[] = {
    12. { "silent", 's', 0, G_OPTION_ARG_NONE, &silent,"do not output status information", NULL },
    13. { "output", 'o', 0, G_OPTION_ARG_STRING, &savefile,"save xml representation of pipeline to FILE and exit", "FILE" },
    14. { NULL }
    15. };
    16. ctx = g_option_context_new("- Your application");
    17. g_option_context_add_main_entries(ctx, entries, NULL);
    18. g_option_context_add_group(ctx, gst_init_get_option_group());
    19. if (!g_option_context_parse(ctx, &argc, &argv, &err)) {
    20. g_print("Failed to initialize: %s
    21. ", err->message);
    22. g_error_free(err);
    23. return 1;
    24. }
    25. printf("Run me with --help to see the Application options appended.
    26. ");
    27. return 0;
    28. }

    6、创建gst元件对象

    1. #include <iostream>
    2. #include <gst/gst.h>
    3. #include <glib.h>
    4. int main(int argc, char *argv[])
    5. {
    6. GstElement *element;
    7. gchar *name;
    8. /* init GStreamer */
    9. gst_init(&argc, &argv);
    10. /* create element */
    11. element = gst_element_factory_make("fakesrc", "source");
    12. //创建一个jst元件
    13. if (!element) {
    14. g_print("Failed to create element of type 'fakesrc'
    15. ");
    16. return -1;
    17. }
    18. else {
    19. g_print("gstelement ok!
    20. ");
    21. }
    22. element = gst_element_factory_make("fakesrc", "source");
    23. /* get name */
    24. g_object_get(G_OBJECT(element), "name", &name, NULL);
    25. //g_object_get获取gobject对象的名字属性
    26. g_print("The name of the element is '%s'.
    27. ", name);
    28. g_free(name);
    29. gst_object_unref(GST_OBJECT(element));
    30. //释放jst元件,必须手动释放
    31. return 0;
    32. }

    元件的四种状态:

    GST_STATE_NULL: 默认状态

            该状态将会回收所有被该元件占用的资源。

    GST_STATE_READY: 准备状态

            元件会得到所有所需的全局资源,这些全局资源将被通过该元 件的数据流所使用。例如打开设备、分配缓存等。但在这种状态下,数据流仍未开始被处 理,所 以数据流的位置信息应该自动置 0。如果数据流先前被打开过,它应该被关闭,并且其位置信 息、特性信息应该被重新置为初始状态。

    GST_STATE_PAUSED: 暂停状态

            在这种状态下,元件已经对流开始了处理,但此刻暂停了处理。因此该 状态下元件可以修改流的位置信息,读取或者处理流数据,以及一旦状态变为 PLAYING,流可 以重放数据流。这种情况下,时钟是禁止运行的。总之, PAUSED 状态除了不能运行时钟外, 其它与 PLAYING 状态一模一样。处于 PAUSED 状态的元件会很快变换到 PLAYING 状态。举 例来说,视频或音频输出元件会等待数据的到来,并将它们压入队列。一旦状态改变,元件就会 处理接收到的数据。同样,视频接收元件能够播放数据的第 一帧。(因为这并不会影响时钟)。自 动加载器(Autopluggers)可以对已经加载进管道的插件进行这种状态转换。其它更多的像 codecs 或者 filters 这种元件不需要在这个状态上做任何事情。

    GST_STATE_PLAYING: 

            PLAYING 状态除了当前运行时钟外,其它与 PAUSED 状态一模一 样。你可以通过函数 gst_element_set_state()来改变一个元件的状态。你如果显式地改变一个元件 的状态,GStreamer 可能会 使它在内部经过一些中间状态。例如你将一个元件从 NULL 状态设 置为 PLAYING 状态,GStreamer 在其内部会使得元件经历过 READY 以及 PAUSED 状态。 当处于 GST_STATE_PLAYING 状态,管道会自动处理数据。它们不需要任何形式的迭代。

    7、查看插件

    1. #include <iostream>
    2. #include <gst/gst.h>
    3. #include <glib.h>
    4. int main(int argc,char *argv[])
    5. {
    6. GstElementFactory *factory;
    7. //声明插件,插件是GstElementFactory
    8. /* init GStreamer */
    9. gst_init(&argc, &argv);
    10. /* get factory */
    11. factory = gst_element_factory_find("audiotestsrc");
    12. //寻找系统里是否有这个插件
    13. if (!factory) {
    14. g_print("You don't have the 'audiotestsrc' element installed!
    15. ");
    16. return -1;
    17. }
    18. /* display information */
    19. g_print("The '%s' element is a member of the category %s.
    20. "
    21. "Description: %s
    22. ",
    23. gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(factory)),
    24. gst_element_factory_get_klass(factory),
    25. gst_element_factory_get_description(factory));
    26. //打印出插件的信息
    27. return 0;
    28. }

    这个功能就像是命令行里的如下命令:

    gst-inspect-1.0   audiotestsrc

    #gst-inspect-1.0 加插件名

    8、链接元件

    1. #include <iostream>
    2. #include <gst/gst.h>
    3. #include <glib.h>
    4. #include <gst/gst.h>
    5. int
    6. main(int argc,
    7. char *argv[])
    8. {
    9. GstElement *pipeline;
    10. GstElement *source, *filter, *sink;
    11. //声明一个源元件,过滤元件和接收元件
    12. /* init */
    13. gst_init(&argc, &argv);
    14. /* create pipeline */
    15. pipeline = gst_pipeline_new("my-pipeline");
    16. /* create elements */
    17. source = gst_element_factory_make("fakesrc", "source");
    18. filter = gst_element_factory_make("identity", "filter");
    19. sink = gst_element_factory_make("fakesink", "sink");
    20. //选择3个插件创建3个不同的元件
    21. /* must add elements to pipeline before linking them */
    22. gst_bin_add_many(GST_BIN(pipeline), source, filter, sink, NULL);
    23. /* link */
    24. if (!gst_element_link_many(source, filter, sink, NULL)) {
    25. g_warning("Failed to link elements!");
    26. }
    27. return 0;
    28. }

    9、箱柜(箱柜本身是一个元件,但是它内部还可以是一串链接起来的元件)

    1. #include <iostream>
    2. #include <gst/gst.h>
    3. #include <glib.h>
    4. #include <gst/gst.h>
    5. int
    6. main(int argc,
    7. char *argv[])
    8. {
    9. GstElement *bin, *pipeline, *source, *sink;
    10. /* init */
    11. gst_init(&argc, &argv);
    12. /* create */
    13. pipeline = gst_pipeline_new("my_pipeline");
    14. bin = gst_pipeline_new("my_bin");
    15. //创建箱柜:gst_pipeline_new和gst_bin_new
    16. source = gst_element_factory_make("fakesrc", "source");
    17. sink = gst_element_factory_make("fakesink", "sink");
    18. /* set up pipeline */
    19. gst_bin_add_many(GST_BIN(bin), source, sink, NULL);
    20. gst_bin_add(GST_BIN(pipeline), bin);
    21. //添加元件到箱柜
    22. gst_bin_remove(GST_BIN(bin),sink);
    23. //从箱柜中移除元件,移除的元件自动被销毁,
    24. gst_element_link(source, sink);
    25. //链接元件,因为sink元件被我移除了,所以可能实际上运行不起来
    26. gst_object_unref(GST_OBJECT(source));
    27. gst_object_unref(GST_OBJECT(sink));
    28. return 0;
    29. }

    10、bus总线

    获取bus总线:gst_pipeline_get_bus

    在总线上添加一个回调函数(官方语言叫watch):gst_bus_add_watch

    1. #include <gst/gst.h>
    2. static GMainLoop *loop;
    3. static gboolean
    4. my_bus_callback(GstBus *bus,GstMessage *message,gpointer data)
    5. {
    6. g_print("Got %s message
    7. ", GST_MESSAGE_TYPE_NAME(message));
    8. switch (GST_MESSAGE_TYPE(message)) {
    9. case GST_MESSAGE_ERROR: {
    10. GError *err;
    11. gchar *debug;
    12. gst_message_parse_error(message, &err, &debug);
    13. g_print("Error: %s
    14. ", err->message);
    15. g_error_free(err);
    16. g_free(debug);
    17. g_main_loop_quit(loop);
    18. break;
    19. }
    20. case GST_MESSAGE_EOS:
    21. /* end-of-stream */
    22. g_main_loop_quit(loop);
    23. break;
    24. default:
    25. /* unhandled message */
    26. g_print("something happend!
    27. ");
    28. break;
    29. }
    30. /* we want to be notified again the next time there is a message
    31. * on the bus, so returning TRUE (FALSE means we want to stop watching
    32. * for messages on the bus and our callback should not be called again)
    33. */
    34. return TRUE;
    35. }
    36. gint main(gint argc,gchar *argv[])
    37. {
    38. GstElement *pipeline;
    39. GstBus *bus;
    40. /* init */
    41. gst_init(&argc, &argv);
    42. /* create pipeline, add handler */
    43. pipeline = gst_pipeline_new("my_pipeline");
    44. /* adds a watch for new message on our pipeline's message bus to
    45. * the default GLib main context, which is the main context that our
    46. * GLib main loop is attached to below
    47. */
    48. bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
    49. //首先获取总线
    50. gst_bus_add_watch(bus, my_bus_callback, NULL);
    51. //然后添加一个消息处理器:设置消息处理器到管道的总线上gst_bus_add_watch ()
    52. gst_object_unref(bus);
    53. /* create a mainloop that runs/iterates the default GLib main context
    54. * (context NULL), in other words: makes the context check if anything
    55. * it watches for has happened. When a message has been posted on the
    56. * bus, the default main context will automatically call our
    57. * my_bus_callback() function to notify us of that message.
    58. * The main loop will be run until someone calls g_main_loop_quit()
    59. */
    60. loop = g_main_loop_new(NULL, FALSE);
    61. g_main_loop_run(loop);
    62. /* clean up */
    63. gst_element_set_state(pipeline, GST_STATE_NULL);
    64. /*gst_element_unref(pipeline);
    65. gst_main_loop_unref(loop);*/
    66. return 0;
    67. }

  • 相关阅读:
    [buuctf][SUCTF2019]SignIn
    C/C++语言100题练习计划 97——素数对
    MySql存储引擎
    2024年抖店的市场已经饱和,小白不适合入局了?真实现状如下
    linux 中的根文件系统
    初识jmeter及简单使用
    Pico,是要拯救还是带偏消费级VR?
    Java项目:SSM简单院校教师工资管理系统
    NR 物理层卷积码 slide 9
    hadoop项目之求出每年二月的最高气温(Combiner优化)
  • 原文地址:https://blog.csdn.net/fanyun_01/article/details/125511163