Commit 429a8ed3844b04dd5d8a8eb88f021ed335dff1fa
1 parent
8da3ff18
Add basic audio functionality to vnc.c
This allows among other things to capturing A/V of running guests. Ad-hoc client that does this (via script that invokes ffmpeg) can be found at: http://repo.or.cz/w/qemu/malc.git?a=tree;f=vcap;hb=capture3 Thanks to Anthony Liguori for comments and review. git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@5850 c046a42c-6fe2-441c-8c8c-71466251a162
Showing
1 changed file
with
131 additions
and
1 deletions
vnc.c
| @@ -28,6 +28,7 @@ | @@ -28,6 +28,7 @@ | ||
| 28 | #include "sysemu.h" | 28 | #include "sysemu.h" |
| 29 | #include "qemu_socket.h" | 29 | #include "qemu_socket.h" |
| 30 | #include "qemu-timer.h" | 30 | #include "qemu-timer.h" |
| 31 | +#include "audio/audio.h" | ||
| 31 | 32 | ||
| 32 | #define VNC_REFRESH_INTERVAL (1000 / 30) | 33 | #define VNC_REFRESH_INTERVAL (1000 / 30) |
| 33 | 34 | ||
| @@ -169,6 +170,9 @@ struct VncState | @@ -169,6 +170,9 @@ struct VncState | ||
| 169 | int client_green_shift, client_green_max, server_green_shift, server_green_max; | 170 | int client_green_shift, client_green_max, server_green_shift, server_green_max; |
| 170 | int client_blue_shift, client_blue_max, server_blue_shift, server_blue_max; | 171 | int client_blue_shift, client_blue_max, server_blue_shift, server_blue_max; |
| 171 | 172 | ||
| 173 | + CaptureVoiceOut *audio_cap; | ||
| 174 | + audsettings_t as; | ||
| 175 | + | ||
| 172 | VncReadEvent *read_handler; | 176 | VncReadEvent *read_handler; |
| 173 | size_t read_handler_expect; | 177 | size_t read_handler_expect; |
| 174 | /* input */ | 178 | /* input */ |
| @@ -592,7 +596,7 @@ static void vnc_update_client(void *opaque) | @@ -592,7 +596,7 @@ static void vnc_update_client(void *opaque) | ||
| 592 | old_row += ds_get_linesize(vs->ds); | 596 | old_row += ds_get_linesize(vs->ds); |
| 593 | } | 597 | } |
| 594 | 598 | ||
| 595 | - if (!has_dirty) { | 599 | + if (!has_dirty && !vs->audio_cap) { |
| 596 | qemu_mod_timer(vs->timer, qemu_get_clock(rt_clock) + VNC_REFRESH_INTERVAL); | 600 | qemu_mod_timer(vs->timer, qemu_get_clock(rt_clock) + VNC_REFRESH_INTERVAL); |
| 597 | return; | 601 | return; |
| 598 | } | 602 | } |
| @@ -681,6 +685,71 @@ static void buffer_append(Buffer *buffer, const void *data, size_t len) | @@ -681,6 +685,71 @@ static void buffer_append(Buffer *buffer, const void *data, size_t len) | ||
| 681 | buffer->offset += len; | 685 | buffer->offset += len; |
| 682 | } | 686 | } |
| 683 | 687 | ||
| 688 | +/* audio */ | ||
| 689 | +static void audio_capture_notify(void *opaque, audcnotification_e cmd) | ||
| 690 | +{ | ||
| 691 | + VncState *vs = opaque; | ||
| 692 | + | ||
| 693 | + switch (cmd) { | ||
| 694 | + case AUD_CNOTIFY_DISABLE: | ||
| 695 | + vnc_write_u8(vs, 255); | ||
| 696 | + vnc_write_u8(vs, 1); | ||
| 697 | + vnc_write_u16(vs, 0); | ||
| 698 | + vnc_flush(vs); | ||
| 699 | + break; | ||
| 700 | + | ||
| 701 | + case AUD_CNOTIFY_ENABLE: | ||
| 702 | + vnc_write_u8(vs, 255); | ||
| 703 | + vnc_write_u8(vs, 1); | ||
| 704 | + vnc_write_u16(vs, 1); | ||
| 705 | + vnc_flush(vs); | ||
| 706 | + break; | ||
| 707 | + } | ||
| 708 | +} | ||
| 709 | + | ||
| 710 | +static void audio_capture_destroy(void *opaque) | ||
| 711 | +{ | ||
| 712 | +} | ||
| 713 | + | ||
| 714 | +static void audio_capture(void *opaque, void *buf, int size) | ||
| 715 | +{ | ||
| 716 | + VncState *vs = opaque; | ||
| 717 | + | ||
| 718 | + vnc_write_u8(vs, 255); | ||
| 719 | + vnc_write_u8(vs, 1); | ||
| 720 | + vnc_write_u16(vs, 2); | ||
| 721 | + vnc_write_u32(vs, size); | ||
| 722 | + vnc_write(vs, buf, size); | ||
| 723 | + vnc_flush(vs); | ||
| 724 | +} | ||
| 725 | + | ||
| 726 | +static void audio_add(VncState *vs) | ||
| 727 | +{ | ||
| 728 | + struct audio_capture_ops ops; | ||
| 729 | + | ||
| 730 | + if (vs->audio_cap) { | ||
| 731 | + term_printf ("audio already running\n"); | ||
| 732 | + return; | ||
| 733 | + } | ||
| 734 | + | ||
| 735 | + ops.notify = audio_capture_notify; | ||
| 736 | + ops.destroy = audio_capture_destroy; | ||
| 737 | + ops.capture = audio_capture; | ||
| 738 | + | ||
| 739 | + vs->audio_cap = AUD_add_capture(NULL, &vs->as, &ops, vs); | ||
| 740 | + if (!vs->audio_cap) { | ||
| 741 | + term_printf ("Failed to add audio capture\n"); | ||
| 742 | + } | ||
| 743 | +} | ||
| 744 | + | ||
| 745 | +static void audio_del(VncState *vs) | ||
| 746 | +{ | ||
| 747 | + if (vs->audio_cap) { | ||
| 748 | + AUD_del_capture(vs->audio_cap, vs); | ||
| 749 | + vs->audio_cap = NULL; | ||
| 750 | + } | ||
| 751 | +} | ||
| 752 | + | ||
| 684 | static int vnc_client_io_error(VncState *vs, int ret, int last_errno) | 753 | static int vnc_client_io_error(VncState *vs, int ret, int last_errno) |
| 685 | { | 754 | { |
| 686 | if (ret == 0 || ret == -1) { | 755 | if (ret == 0 || ret == -1) { |
| @@ -712,6 +781,7 @@ static int vnc_client_io_error(VncState *vs, int ret, int last_errno) | @@ -712,6 +781,7 @@ static int vnc_client_io_error(VncState *vs, int ret, int last_errno) | ||
| 712 | } | 781 | } |
| 713 | vs->wiremode = VNC_WIREMODE_CLEAR; | 782 | vs->wiremode = VNC_WIREMODE_CLEAR; |
| 714 | #endif /* CONFIG_VNC_TLS */ | 783 | #endif /* CONFIG_VNC_TLS */ |
| 784 | + audio_del(vs); | ||
| 715 | return 0; | 785 | return 0; |
| 716 | } | 786 | } |
| 717 | return ret; | 787 | return ret; |
| @@ -1138,6 +1208,15 @@ static void send_ext_key_event_ack(VncState *vs) | @@ -1138,6 +1208,15 @@ static void send_ext_key_event_ack(VncState *vs) | ||
| 1138 | vnc_flush(vs); | 1208 | vnc_flush(vs); |
| 1139 | } | 1209 | } |
| 1140 | 1210 | ||
| 1211 | +static void send_ext_audio_ack(VncState *vs) | ||
| 1212 | +{ | ||
| 1213 | + vnc_write_u8(vs, 0); | ||
| 1214 | + vnc_write_u8(vs, 0); | ||
| 1215 | + vnc_write_u16(vs, 1); | ||
| 1216 | + vnc_framebuffer_update(vs, 0, 0, ds_get_width(vs->ds), ds_get_height(vs->ds), -259); | ||
| 1217 | + vnc_flush(vs); | ||
| 1218 | +} | ||
| 1219 | + | ||
| 1141 | static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings) | 1220 | static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings) |
| 1142 | { | 1221 | { |
| 1143 | int i; | 1222 | int i; |
| @@ -1169,6 +1248,9 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings) | @@ -1169,6 +1248,9 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings) | ||
| 1169 | case -258: | 1248 | case -258: |
| 1170 | send_ext_key_event_ack(vs); | 1249 | send_ext_key_event_ack(vs); |
| 1171 | break; | 1250 | break; |
| 1251 | + case -259: | ||
| 1252 | + send_ext_audio_ack(vs); | ||
| 1253 | + break; | ||
| 1172 | case 0x574D5669: | 1254 | case 0x574D5669: |
| 1173 | vs->has_WMVi = 1; | 1255 | vs->has_WMVi = 1; |
| 1174 | break; | 1256 | break; |
| @@ -1476,6 +1558,48 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len) | @@ -1476,6 +1558,48 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len) | ||
| 1476 | ext_key_event(vs, read_u16(data, 2), | 1558 | ext_key_event(vs, read_u16(data, 2), |
| 1477 | read_u32(data, 4), read_u32(data, 8)); | 1559 | read_u32(data, 4), read_u32(data, 8)); |
| 1478 | break; | 1560 | break; |
| 1561 | + case 1: | ||
| 1562 | + if (len == 2) | ||
| 1563 | + return 4; | ||
| 1564 | + | ||
| 1565 | + switch (read_u16 (data, 2)) { | ||
| 1566 | + case 0: | ||
| 1567 | + audio_add(vs); | ||
| 1568 | + break; | ||
| 1569 | + case 1: | ||
| 1570 | + audio_del(vs); | ||
| 1571 | + break; | ||
| 1572 | + case 2: | ||
| 1573 | + if (len == 4) | ||
| 1574 | + return 10; | ||
| 1575 | + switch (read_u8(data, 4)) { | ||
| 1576 | + case 0: vs->as.fmt = AUD_FMT_U8; break; | ||
| 1577 | + case 1: vs->as.fmt = AUD_FMT_S8; break; | ||
| 1578 | + case 2: vs->as.fmt = AUD_FMT_U16; break; | ||
| 1579 | + case 3: vs->as.fmt = AUD_FMT_S16; break; | ||
| 1580 | + case 4: vs->as.fmt = AUD_FMT_U32; break; | ||
| 1581 | + case 5: vs->as.fmt = AUD_FMT_S32; break; | ||
| 1582 | + default: | ||
| 1583 | + printf("Invalid audio format %d\n", read_u8(data, 4)); | ||
| 1584 | + vnc_client_error(vs); | ||
| 1585 | + break; | ||
| 1586 | + } | ||
| 1587 | + vs->as.nchannels = read_u8(data, 5); | ||
| 1588 | + if (vs->as.nchannels != 1 && vs->as.nchannels != 2) { | ||
| 1589 | + printf("Invalid audio channel coount %d\n", | ||
| 1590 | + read_u8(data, 5)); | ||
| 1591 | + vnc_client_error(vs); | ||
| 1592 | + break; | ||
| 1593 | + } | ||
| 1594 | + vs->as.freq = read_u32(data, 6); | ||
| 1595 | + break; | ||
| 1596 | + default: | ||
| 1597 | + printf ("Invalid audio message %d\n", read_u8(data, 4)); | ||
| 1598 | + vnc_client_error(vs); | ||
| 1599 | + break; | ||
| 1600 | + } | ||
| 1601 | + break; | ||
| 1602 | + | ||
| 1479 | default: | 1603 | default: |
| 1480 | printf("Msg: %d\n", read_u16(data, 0)); | 1604 | printf("Msg: %d\n", read_u16(data, 0)); |
| 1481 | vnc_client_error(vs); | 1605 | vnc_client_error(vs); |
| @@ -2177,6 +2301,11 @@ void vnc_display_init(DisplayState *ds) | @@ -2177,6 +2301,11 @@ void vnc_display_init(DisplayState *ds) | ||
| 2177 | 2301 | ||
| 2178 | vnc_colordepth(vs->ds, 32); | 2302 | vnc_colordepth(vs->ds, 32); |
| 2179 | vnc_dpy_resize(vs->ds, 640, 400); | 2303 | vnc_dpy_resize(vs->ds, 640, 400); |
| 2304 | + | ||
| 2305 | + vs->as.freq = 44100; | ||
| 2306 | + vs->as.nchannels = 2; | ||
| 2307 | + vs->as.fmt = AUD_FMT_S16; | ||
| 2308 | + vs->as.endianness = 0; | ||
| 2180 | } | 2309 | } |
| 2181 | 2310 | ||
| 2182 | #ifdef CONFIG_VNC_TLS | 2311 | #ifdef CONFIG_VNC_TLS |
| @@ -2269,6 +2398,7 @@ void vnc_display_close(DisplayState *ds) | @@ -2269,6 +2398,7 @@ void vnc_display_close(DisplayState *ds) | ||
| 2269 | vs->subauth = VNC_AUTH_INVALID; | 2398 | vs->subauth = VNC_AUTH_INVALID; |
| 2270 | vs->x509verify = 0; | 2399 | vs->x509verify = 0; |
| 2271 | #endif | 2400 | #endif |
| 2401 | + audio_del(vs); | ||
| 2272 | } | 2402 | } |
| 2273 | 2403 | ||
| 2274 | int vnc_display_password(DisplayState *ds, const char *password) | 2404 | int vnc_display_password(DisplayState *ds, const char *password) |