Graphics and Media • 1:06:24
Leverage the power and flexibility of QTKit to add advanced multimedia capabilities to your application. In this session, we will present practical, hands-on coverage of the QTKit framework, which offers a rich API for manipulating time-based media. Bring your laptop and follow along as we go deep in QTKit code.
Speaker: Tim Monroe
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Okay, thanks a lot for coming. We've got a lot of good stuff to show you today, so let's just jump right in. In this session, we're going to first talk a little bit about what we've learned in QTKit as it exists in Panther and Tiger, so some of the accumulated wisdom that we've gotten by helping people use it.
Then we're going to jump into new stuff in Leopard. We're going to talk about some new methods that we've added to existing classes. We'll talk about the 64-bit operation. I know some of you are interested in that. And we'll spend actually most of our time on the new capture classes. QTKit has more than tripled in size in Leopard, and so I can't talk about everything, especially since this is a hands-on lab. So we're going to be looking at code, working through code, and actually building applications from scratch in here using QTKit.
So what have we learned in the past year, year and a half, working with QTKit in Tiger and Panther? Well, we found out that there were some bugs in it, and we fixed those. And by far, my favorite bug is one that concerns the movie creation time or the movie modification time attribute of a QT movie.
It turns out that if you created a movie right now, and then you use QTKit to determine the movie creation time, you'd get a time that was 13 hours ago. And actually, the number of hours that you were wrong depended upon where in the world you were. So if you were in Burma, you'd actually get the correct time.
When we stood up here last year, we told you that you can't subclass QTMovieView and correctly override the event handling methods. So we fixed that, and in a second I'll show you exactly how that works. And finally, there's an issue that comes up quite often on the mailing list about, why am I not getting the correct answers when I query some movie properties? And so there's an issue or two there that I'd like to talk about.
So let's talk about subclassing QTMovieView. As I said, it did not work correctly in QuickTime 7.0. A QTMovieView is a subclass of NSView. You expect to be able to subclass it and override some of the NSView methods. Well, you couldn't do that. But we did fix that in 7.04. So you've had, for quite a while, a version of QTKit where you could do that.
And let's just look at a demo of that. Could I have the first demo machine? Now, there is one technical glitch I have to mention that I can only see what's down here unless I turn around because of some tricks with the capture that's going to come up. So if it looks like I'm squinting down here, it's because I am. Is that my project file? OK. And is it launching? No, it's not.
So this is just a standard document-based application that I'm opening up. Does it not want to open? Here we go. And since what I want to do is create a subclass of QTMovieView, let's just look at the declaration there. And you see that my movie view is declared as a subclass of QTMovieView. And you can also see that I'm overriding just one method in there. So let's-- oh, no, I didn't hit Quit. I hit Command-W. Let's try that again.
This time I'll hit Command-W. And let's look at the implementation of that. As you can see, as I said, I'm overriding only one method. And in this particular case, what I want to do is prevent the user from stepping through the movie. So I look for those key-down events that have the left arrow key or the right arrow key, and I'm just going to do nothing if I hit one of those. Otherwise, I'll just process it in the standard way. So there is my subclass of QTMovieView. Let's build and run this.
In a second it will come up. And let's open a movie. Oh, let's just pick that one there. So here I have a movie, and just to make clear, I'm going to, you can see that I'm hitting the left and right arrow keys and nothing has happened. All the other keys, for instance, the space bar, does in fact start the movie playing. There's one other thing I want you to notice, which is on the controller bar, you'll see that on the right-hand side of the controller bar, where you normally would have the little step buttons, they're not there.
How did I do that? Let's look now at my document class, and you'll see right after I open it and assign a movie to the view, I call this new method, set step button visible, no. So that's a new method that I'll talk about in more detail later that allows me to hide those step buttons.
So now if we could go back to slides, please. So there's a simple case of where I was able to do what you thought you could always do or always should have been able to do, which is subclass the view object and override its methods. Now let me talk about this issue about operating on newly opened movies. The essential thing you have to keep in mind is that all of the QT movie initialization methods operate asynchronously. That means no matter which of them you call, we're gonna give you back a QT movie object right away.
Even before possibly any of the movie data is available to us, we're going to give you a valid QT movie object. So some people want to do something like this. They want to call movie with URL, passing in a URL, and then immediately they say, well, all right, give me the natural size attribute. Because I want to say, no, how big to make my movie view so I can draw things the correct size. Or they might say, all right, let's play it.
The trouble with this, as I said, is that the movie with URL method is going to return virtually immediately with a valid QT movie. But the server that's got that URL could be off in Burma, for all we know. And it could be a real slow server. So by the time we get that QT movie object, there could be none of the movie metadata, that is to say the movie Adam, available. And there could be none of the movie media data available. So we're not going to be able to query any of the movie properties. Let alone the movie data. on the natural size, and we're not going to be able to start playing it.
So how would we actually do this right? One way is to force the movie to open synchronously. And you would do that by using a different movie opening method, that is, movie with attributes. And you'll set up a dictionary where you specify the URL, and you also set the QTMovieOpenAsyncOK attribute to null.
So essentially, you would be saying, it's not OK to do that fancy asynchronous opening. I want you to take your time, get all the movie data, and then return to me. So if you do this, then you can't-- this is good code. You could then call the attribute for key, looking for any of the movie properties, and you could start the movie playing.
What's wrong with this method is that, again, if you've got a slow server, you're just going to be blocking your application for as long as it takes for every little piece of that movie to download. And what's even worse is that you don't need every little piece of the movie in order to do either of the things you want to do.
In order to determine the natural size of the movie, you just need the movie atom, which is probably a couple thousand bytes or maybe even less at the beginning of the movie, and you can get that real quick. And also, to start the movie playing, you don't need to have all the movie media data available. You just need enough of the data such that if you were to start playing now, by the time all of the data arrived, you'd be at the end.
So the better way to handle this issue is to monitor what we call the movie load state. And we do it like this, and I apologize for that huge chunk of code. But once again, we could use movie with URL to open the movie. And again, that will execute asynchronously. And then what we do is register for a notification. In this case, we want to look for the movie load state did change notification. And when we get that notification, we'll call this load state changed method.
And you can see what I'm doing is looking for two different thresholds. I apologize for the hard-coded constants. It turns out that QTKit doesn't help us out here. In movies.h, there are a couple of constants, but probably you're not including movies.h. So we will add some constants to QTKit so you don't have those ugly 10,000 and 20,000 in there. So when you get to the 10,000 state, that means that your movie is playable. And what that really means is that the movie atom is available.
So at that point, you can query the movie properties. You could ask for the natural size. You could ask for the duration, anything you wanted to know about it. The next threshold that we're interested in is 20,000. And what that means is that the movie is now playable through to the end. It doesn't mean that all the data is there. It just means that by the time we got to some data we needed, we think it would be there.
A third possibility that you have now in Leopard is to use the autoplay method. And we could do that like this. We could, again, open the movie using movie with URL. That will execute asynchronously. So right away, we'll get a movie back. And then we can call this new method autoplay. And autoplay just means what I've said a couple of times already. Go ahead and start the movie when you think you've got enough data to play it through to the end uninterrupted. Okay, now let's talk about some new APIs we've added to the existing classes.
In QTMovie, we've added five general categories of methods. We have some methods that allow you to create writable movie containers. I'll explain that more in a minute. We've got some utilities that will help you execute QTMovie, sorry, QTKit methods on a thread. We have this autoplay method that we just saw, so we don't need to talk about that really much more. We have some methods that allow you to tag movies with the visual aperture mode information.
I'm not going to explain that in great detail. I hope you were at the talk yesterday, or the modern video talk, I believe it was today, where he talked in great length about the four different aperture modes and what the exact meaning of those was. And finally, we have some methods that allow you to create chapters in a movie.
A chapter is a named segment of a movie. The asterisk on that one means that it's not in the seed that you have now. We haven't gotten the implementation fully in there, but we will have those in Leprechaun. Bye. We've added one class of methods in QTMovieView, which are what I call controller bar button methods. And we already saw one of those, where I was able to hide the step buttons in the movie controller bar.
In QTTrack, we also have the track analogs of the visual aperture mode methods. And we have a very nice new attribute called the format summary attribute. So here's what declarations look like. The QTMovie writable movie container methods. The idea is this. When QTKit first came out, the methods that we supplied allowed you to open existing QuickTime movies and to edit them. There was no facility whatsoever to create new QTMovie objects and have things work out the way you think they should work out. And believe it or not, people wanted to create new movies.
So DTS put together some sample code that helped them in this, and we'll actually look at that in a second. But the best solution is for us to give you a method where you can say, "Here's a file name. Create a new file there and give me back a QTMovie, and if I add any data to that QTMovie, it should go into that file." And as you can see, we also have the ability to do that to a block of memory and data or to an arbitrary data reference.
We've added a bunch of threading utilities. If you've worked with QuickTime, you know that there are some conditions about calling things on background threads. And if you've actually worked with it, you've had to resort to some C-level APIs in order to make sure that things are as thread-safe as they can be under QuickTime. And if you've worked with them, these names here should not surprise you. And we'll look at these in more detail later.
Autoplay, enough said. We've seen it in operation. QT Movie Visual Aperture Mode methods. Again, on the movie level, the first method here, Generate Aperture Mode Dimensions, will take a movie, and it will go through all the tracks in the movie, and it will take its best guess as to what the various aperture mode settings should be. So if you trust us, you can just call Generate Aperture Mode Dimensions on a QT Movie object, and you're done.
Or if you don't trust us, you can go ahead and remove the work we did. Now, on the track level, we also have some Visual Aperture Mode methods. Again, if you trust us, you can call on a QT track, Generate Aperture Mode Dimensions, or if you don't trust us, you can say, for this given aperture mode, and the four aperture modes are listed here at the bottom, I want you to set the dimensions for that mode to this NS size that I'm passing in. So you have complete control over setting these Visual Aperture Modes settings.
The chapter methods are going to look like this. Again, they're not in your seeds. Actually, play with them. So you'll be able to determine whether a movie has some chapters. If it has them, how many it has. You can get an array of the chapters, and that array will include for each chapter the string, which is the chapter title, and the start time of that chapter.
The fourth method there I think is most interesting. You can add some chapters to a movie. I think that will be very useful to people. Finally, you can remove chapters. And the last two methods allow you to navigate within a movie, either by chapter index or given a time to find which chapter is at that time. This looks like a lot of new methods, but really it's just variations on a theme. Namely, for a given button in the movie controller bar, tell me whether it's visible or not, and also set it to whatever state I tell you to set it to.
Finally, in QTTrack, we've got this new attribute, the track format summary. And what that does is hand me back an NSString, which is a human-readable, localized description of the track's format. And we'll look at that later. So now let's jump in and do some work. Let's look at these writable file-- no, I want to stay on slides, please.
Okay, so as I said, when QTKit first came out, we had no facility in the kit to allow you to create an empty movie file and add data to it. And people, as I said, wanted to do that for some reason. And we so got together with DTS and concocted some sample code that sort of kind of worked and allowed them to do what they wanted. And the key here is to call the C-level API createMovieStorage.
What that does is create a new container at the specified location and return to you in the last parameter a QuickTime movie that's associated with that file or URL or memory location or whatever. So the first yellow line there takes a file name and gives us back what we call a data reference. In fact, it gives us back a data reference and the type.
And as you can see, we are passing those into the createMovieStorage call. So that's what we do at the C-level. And then in kit, we will create a new QT movie by calling the movieWithQuickTimeMovie method. And then, of course, we need to set that movie to be editable because by default, a QT movie is not editable.
So that's the setup we'd have to do. Now we could add some data to the movie, and we might do it like this. There's this nice add image for duration with attributes method in QTMovie. And we would get an image here. We're reading it out of our bundle. We'll specify a dictionary of settings as to what codec we want that image to be saved with. And then we call add image, and the image gets added to the movie.
Oh, we're not done yet. So you've added this image to the movie. You think you could be able to call the general method that updates a movie file, namely update movie file. No. This old way has the problem that that will fail. So what you need to do now is take that movie that is on disk and has data in it and flatten it into yet a second movie.
So that's why I'm calling the right to file with attributes, passing in a dictionary where I say, I just want to flatten this movie into this new location. Well, that's tedious. So take all three slides that I've just shown you and put them into this one. This is the value of this new method, this initToWritableFile. We start by calling initToWritableFile, passing it a path name. We'll then do the standard add image. That hasn't changed. And we end it all up by calling updateMovieFile, and we're done. That file on disk now has all the data in it that we want it to have.
So now let's do a demo of this. Is that my writable file demo? Is that my project? All right, let's look at the code that I'm going to run. You can see this is just like what I had on the slide, except that instead of adding one image to the movie, I'm adding three here. Again, they're just all stored in my bundle. And then I call updateMovieFile. And you'll notice that at the end, I'm going to set the movie on the view, so that once I've created the movie, it should pop up in my view. So let's run that.
So I just click Create Movie real quickly. The movie is created on disk and comes up in my movie view. And if I go through it, you can see it has three frames, and each one lasts for three seconds. Now, just for those doubting Thomases in the audience, you can see that I wrote the file into slash temp slash new movie. So let's go to slash temp.
And is there something called new movie there? Yes, there is. Let's open new movie. This will come up in QuickTime Player, and it's the movie we just saw come up in the view. So we did take three images from our bundle, put them into a movie, and the movie exists on disk.
Okay, can we go back to slides, please? So, some people will find that a very useful method, and we've added it in. Now, let's talk about threading. QTKit is built on QuickTime. QuickTime is very old. QuickTime has some issues with threading. QuickTime is not, in general, thread-safe. In order to do QuickTime operations on secondary threads, you need to jump through some hoops.
The primary hoop that you have to jump through is this. QuickTime wants to operate on a movie on only one thread at a time. So, I can't be doing something to a movie on the main thread and also on a background thread. So, it is incumbent upon me, as a developer, to tell QuickTime when I want to stop working on a movie on one thread and start working on it on another thread.
So this is what you'd think, as a Cocoa programmer, you ought to be able to do. Open a movie that's in a file, and then call the standard detached new thread selector to run some method on a background thread. So that's what I think you ought to be able to do.
And then on the background thread, you'd think that it should look like this. You create and release your auto-release pool. You always do that on a background method. And then you should be able to say, export the file in that method. So that's what you'd like to do, and that's what an untutored Cocoa programmer would do. And you'd run into problems if you did that.
Again, as I said, it's your job to tell QuickTime when you no longer want to work on a movie on a given thread. So in the main thread, we'll open the movie, and then we'll call this C-level API, Detach Movie from Current Thread. That just says to QuickTime, I'm done with the movie on this thread. And then you do your Detach New Thread Selector. Okay? So that's all you really have to do there. In Leopard, we've given you a method that makes things have a little bit better Cocoa flavor, namely detached from current thread on a QT movie.
And then, on the background thread, this is what you need to do. You need to attach that movie to the current thread. Then you need to do your work. And then you're probably going to want to detach it from the current thread. But before you do any of that, you have to tell QuickTime, oh, by the way, I'd really like to do some QuickTime stuff on this thread. QuickTime needs to know that, too. So we call this Enter Movies on Thread.
is the developer of the QTKit function. And then when we're done, we're going to call exit movies on thread. Once again, we've got some new QTKit methods that make it look more Cocoa, and so you don't have to dip down into the C-level API anymore, and they look like that.
Okay, there's one more issue. So that's the snippet that I just had of the old style, pre-leopard way of doing things. There's one little thing you can do to make your app a little bit more bulletproof, and that is to stop the movie from being idled on that background thread.
So one could consider this to be a bug in QTKit that it will continue to idle a movie on a background thread when it really shouldn't. So this actually, this call exists pre-leopard. So you can call this as long as you want to. Tim Monroe As long as you do the response to selector thing to make sure you're safe. Let's look at a demo of this now.
So let's look at the code I'm going to be running. So when I click the Export button that you'll see in my document window, I'm going to-- called the detach from current thread method that I talked about. So I'm telling QuickTime I'm going to stop working with that movie on this thread. And if that completes successfully, I want to then do the detach new thread selector thingy.
And notice one other thing I've done is I'm setting the movie on the movie view to nil. That's so that the user doesn't try and say play the movie while it's being operated on in the background thread. So I'm just being safe here. And then the method that I want to run in this background, we'll call enterKitOnThread. Now the reason I have that commented out line there is that in the code that you have, it's the other line that's commented out. And that's the operative line. So we got it backwards there. So forget that that big long line is there. You never saw it.
Then what I'm going to want to do is export this movie into a new format by calling write to file, which is my standard method for exporting or flattening or doing anything to a movie. And the dictionary I'm passing in says, yes, I'd like you to export that movie. And if you could, would you please export it to 3GPP format? So when I've done that, I will then do the detach from current thread so that I can operate on the movie back on the main thread and call execute QTKit on thread.
And one final thing I've added here is that I want to let the main thread know that it's safe to put the movie back in the movie view. So I'll do this perform selector on main thread and the selector is It's up top there. It's finish export, and the finish export just attaches the movie back to the main thread and sets the movie back into the view. So let's run that.
So first thing I need to do is open a movie. So I'll just open our favorite little three-frame movie. I'm going to export it to 3GPP format. Notice that the view goes black because I have disassociated the movie from the view. That was pretty quick. It came back right away.
So if I'm lucky, where did I export it to? Slash temp slash exported movie. So let's again look here. There is exported movie. Let's open it. And you'll notice that this is a smaller version of the movie we saw because this is in 3GPP format, that is to say, suitable for streaming to your cell phone. So let's close that up and go back to slides, please.
Okay, the next thing I want to show is hiding and showing controller bar buttons, and we've sort of seen that, so I'll go quickly. So if I could go to, well, here's the basic thing. We've already seen these. Are step buttons visible? Well, tell me if the step buttons are currently visible, and then setting them visible. Here, I'm just toggling the state of that. So let's please go to the demo machine, please. Okay.
Let's look at the code. It's very simple. It's just a bunch of IB actions that read the current state of whatever button I'm telling it to toggle and then toggles the state. So nothing exciting there. Let's run it. I will open a linear movie, our favorite movie, and you can see that I can say hide and show the step buttons. I can hide and show this so-called custom button. That's useful for you to say to stick a menu off that, so when the user clicks there, they could pull down a menu. Now let me open a QuickTime VR movie.
[Transcript missing]
And finally, let's look at this track format demo. The idea here is we've added a new attribute on a QTTrack object that lets me get a human-readable, localized version of the format of the track. So here I'm taking all the tracks, and I'm just taking the first track in the movie, asking for its track format summary. And I will get back an NSString that looks perhaps something like this if it's an MPEG-3 track.
So let's again just do a quick demo of that. We can look at the code, but it's just sort of what you've already seen, with the minor exception that this actually iterates through all the tracks in the movie and builds up one big string that encapsulates the information about all those tracks. So let's run that.
[Transcript missing]
You didn't see that. Let me go back to my project window. I will reveal to you why that was red, which is that we built and ran a 64-bit app. We really would like to be in 32 bits, don't you think? Let's do that in 32 bits.
Okay, so this should work better. We will open that same movie. And now we see that this movie, I've printed out the format summary there. It has five tracks. Four of them are soundtracks. There are two pairs that are identical in format. And then there's an H.264 video track there. Okay.
Very straightforward. Could I have slides, please? So those are the new APIs, or at least a selection of the new APIs we've added to the existing classes in QTKit. Let's talk a little bit about 64-bit operation. As you've heard in several of the talks already, if you need to get quick time operations in your 64-bit app, you need to use QTKit. The API that you're going to use is identical to the 32-bit API, with a couple of exceptions.
First of all, and most importantly, you cannot use any methods that access the native QuickTime data types. So the two that I've listed, QuickTime Movie and QuickTime Movie Controller, are not available to you in 64-bit. Those are what you use to get the QuickTime Movie or the QuickTime Movie Controller that you can then call the C API on.
Well, there is no C API for QuickTime in 64-bit, so they'd be pretty useless for us to provide those. So let's do a demo of 64-bit. You've already seen one. But just for the heck of it, I'm going to do it over here on this new Intel machine. Here we go.
And it was called, I'm running the same app that I just ran, namely Track Format Demo. And I will open that same movie. And it was called, I'm running the same app that I just ran, namely Track Format Demo. And I will open that same movie. Some of you might not believe that that's really 64-bit, but I can prove it to you because I can run ps-l. Let's try that again.
This lists all the running processes, and one nice thing about the "-l" option is that it prints this flags field here. And if I can find my running program... This is it right here. And I know that because it says 4004. The final four there is a flag that means this is running in 64-bit space. And if you look at that column, you'll see that nothing else is running in 64-bit space. So indeed, I am running this as a 64-bit application. And I'll stop there. Thank you.
Can I go back to slides? Thank you. To me, it's kind of hard to believe that that actually works, right? I mean, we've said QuickTime is not available in 64-bit, but QTKit is available. Well, that's sort of an evasion. That doesn't explain how it works, right? I mean, QTKit depends on QuickTime.
So how do we get this to work? Normally, we don't give you implementation details, which is what I'm about to give you. And the only reason I'm giving you these is because in the seed you have, if you want to build 64-bit apps, you might need to know some of this, with the caveat that these are subject to change. So we have QTKit, and it exists as a 64- and a 32-bit framework. A Cocoa application or indeed a Carbon application can link against the 64-bit QTKit framework. And it can call QTKit methods such as MoviePlay.
The way we've implemented this is to have a client and server model running here. So the client essentially is the 64-bit QTKit framework. And as you can see, the implementation of that play method is just to make an IPC call over to QTKit server. And we're using MIG-based IPC here, so we've got Mach messages running between these two, so we're getting really good performance. And the rest of the story should be pretty obvious. QTKit server links up with the 32-bit version of QTKit, which then accesses QuickTime capabilities. And you've seen it work. I mean, it actually works. So that's pretty amazing to me.
There are some limitations. In the seed that you have, the capture classes are not available. I'll talk about those in just a second. There's no reason they couldn't have been there, it's just there was a deadline we had to meet and they didn't make it in. I fully expect that in the next seed you get, the capture classes will be available in 64-bit.
You cannot currently use QTMovie delegate methods. I doubt that anybody here could actually tell me one of the five QTMovie delegate methods, so you're probably not using them, so that's probably not a big problem. You cannot do drag and drop editing out of a QTMovie view in 64-bit.
Again, that's simply a limitation of the seed deadline. That should be fixed real soon. You've already seen number four there. There are some drawing glitches. So you saw that big red rectangle where there should have been a movie view. That's the glitch. And occasionally, and this is why I'm giving you the implementation details that I really shouldn't be giving you, you might need to kill the server or you might need to restart the server.
The server should restart automatically. It's managed by launchd, but launchd has this feature that if it finds a server that it thinks is a bad boy, it'll give him a timeout and it won't relaunch him ever. So you might need to do something like this. So just do man launch control and you'll see exactly what this means. All right, now what you're all here for, I'm sure. Capture in QTKit.
As I said yesterday, the important thing for you to understand is that the new capture classes that we're offering you are not just a wrapper on the sequence grabber. We could have given you that two years ago. In fact, if you go out on the web and spend a little time with Google, you can find four or five sets of Cocoa classes that are wrappers on the sequence grabber. We didn't want to do that. Sequence grabber is so 1991 technology. You don't want that. You want something built on a brand new capture engine. You want something built on an engine which, for instance, absolutely will not drop frames. That's one quirk of the sequence grabber.
So this new engine gives us the capabilities that I've listed there. You get very, very accurate audio-visual synchronization. Again, Sequence Grabber had its problems. You can get frame-accurate capture. By that I mean, you can say, I want you to start capturing at exactly this time code, and I want exactly 245 frames in the file that you write out, and you'll get it.
We simply don't drop frames. You get access to the transport controls of your camcorder, so you can fast-forward and rewind the tape. As you saw yesterday, you get HDV capture if you have the appropriate codecs installed, and there's a whole lot more capabilities that I simply don't have time to mention.
So what devices are supported? The internal and external eyesight cameras, of course, are supported. HGV devices. Again, if you happen to have Final Cut Pro installed, then you can capture from one of these nice HDV camcorders. You can capture from Core Audio HAL devices. So everything that's in yellow there is in the siege you have.
The two white ones are yet to be implemented or yet to come to you. That is to say DV devices, and we are going to grandfather in sequence grabber video devices. So if you've got an existing device that gives you video through sequence grabber channels, we'll be able to use it.
Let me go back. I don't want to show you that yet. As I said, QTKit has tripled in size. We went from five classes to 16. Those 11 new classes are all capture classes. It's a very complex new architecture that we're giving you because it's built on a pro-grade foundation, and we want to give you the capabilities you need.
I'm not going to talk about all 11 classes today. We don't have time and I frankly don't understand all 11 classes. So what I want to do is give you a simplified picture and I'm going to actually build a capture application from scratch so you can see that you don't need to know those 11 classes. Some of those are very specialized.
The basic idea for QTKit capture is this. You have an object called a QTCaptureSession. That's your capture object. That's what you're going to use to start and stop your capture. This capture session gets its data from somewhere. We call that an input. So that's going to be your camcorder or your eyesight. And this capture session wants to put its output somewhere.
And you'll see that there are two outputs that I'm going to show you today. One is a file. You want to write the data to a file. The second output is the, you want an on-screen preview of what you're capturing. So from the one input through the capture session, you're going to get a preview into a QTCapture view, and you're going to write the data into a file. So that's the real basic architecture you need to understand. The capture session ties it all together. You have some inputs, and you have some outputs. So now let's look at this diagram. There in the middle is our capture session. It's managing a QTCapture device input.
That is to say, an input that's associated with a device. Now, you could imagine there might be other sorts of inputs. We haven't implemented any in this seed, but in the future, we can. We've got the architecture there to be able to plug things in. The capture device input is associated with a capture device, which is just a representation of the camcorder. The outputs that I'm showing here are a QTCapture movie output. That's what manages the writing into the file, and that is associated with a QT movie.
On the other side, the output is, well, ultimately to the capture view, what you're previewing on screen. And associated with the capture view is an output called a QTCaptureVideoPreviewOutput. Now, if you build a capture view, you don't need to worry about that other object. It will take care of creating the video preview output object itself. The reason I highlight it is because maybe you don't want a preview into our view. Maybe you want to take the captured video and composite it on an OpenGL surface. At that point, you are going to need to work with the video preview output object.
So here are, again, not all, but some of the main capture classes. The capture session ties it all together. The capture input gives us the capture device input. The capture output, as you can see, there's the movie output and the video preview output. And that video preview output might be transparent to you because it's handled by the capture view.
Let's make a real quick tour through some of these classes and look at the methods that you're likely to use. Again, for the, I don't know how manyth time, the main object you're going to work with is the capture session. You're going to add inputs and add outputs to it. So the top two methods that I'm showing there are ones you're certainly going to call. You can also remove outputs and inputs. And if you want, you can get arrays of the current outputs and inputs.
And as I said, the capture session is where you're going to do most of your capture manipulation. So you can find out if I am capturing. You can start capturing. And then you think, well, there ought to be a stop capturing call. Well, here we've got a real mouthful. Stop capturing with post-processing delegate.
Why do we need that? Why can't I just say stop capturing? The capture session, or the output object, is going to write stuff into a file, a temporary file that you cannot get at, really. There are delegate methods that allow you to look at the frames as they come in and decide whether you want them written to the file or not.
When we want to stop capturing, we're going to want to do something with that temporary file. Later, what I'm going to do with it is flatten that file into a different file. Because if I'm writing data in and then I say stop capturing with post-processing delegate, at that time the movie Adam gets written to the file, it's at the end of the file where I don't really want it. I want it at the beginning of the file, so I'm going to want to do an export. Or a flatten, I'm sorry.
Capture device methods. So we can get an array of all the available input devices. That's how we're going to know what to select. We can get the display name of a given device. We can find out if it's connected or whether another application is using it. At the device input level, we can create a device input object from a given device. Or we can get the device, the capture device associated with a capture device input.
The movie output, there isn't a whole lot there. The main thing we're going to want to do is get the QTMovie that's associated with that QTCapture movie output object. The video preview output. Again, I said, if you're using the capture view, you're not going to need to worry about this object.
If you want to do fancy stuff, which we'll demonstrate in a little while, such as composite the captured video onto an OpenGL surface, you're going to want to set the visual context on one of these objects. That's how, essentially, you would accomplish that. And then a capture view, you will want to associate it with a capture session, and then we have some view-related methods there that are pretty standard. Fill color, preserves aspect ratio, and so forth.
There are also a few notifications. If a device gets connected or disconnected, your application can find out about it. You might want to do that in order to rebuild, say, a pop-up list of available devices. And you may want to know when a capture session will start or will end.
The last four notifications, I believe, are not implemented in the seed you have. Transport controls you will get by modifying the playback mode attribute of a capture device. You'll pass in a dictionary and there will be settings for the playback speed and whether it's displaying video on its little monitor. So let's do a demo.
All right, how brave am I today? Let's hope the demo gods are with me because Thank you. All right, do we have Xcode running? Okay. New project. So I'm starting from nothing. Oh boy, tell me I can read that. When I get to Cocoa document-based application, is that it? Is that it? Excellent. Am I good? All right. So I'm going to build a Cocoa document-based application. Okay. This is stationary that's out of the box. Let's call this WWDC demo.
Okay, so there's my standard new Cocoa document-based application stationery. First thing I want to do, because it's really easy to forget when you're up here on stage, is add in the-- QTKit framework, right? It's going to be real hard to do capture if I don't have the QTKit framework in there. Oh, goodness.
System. Library. Am I getting near frameworks? Oh, this is much easier. All right. Then let's go down to... All right. You never knew programming could be so hectic. All right, so I've added QTKit framework to my project. Now I'm going to set up my UI, and I should have resources there. So I'm going to open my main document nib there. All right. We don't need that wonderful text. And what I do want is a QTCapture view. So you can see this new icon here. I'll just drag it over in the standard way. Drop it there.
[Transcript missing]
All right, we'll do it like that. And I want to set the size of this to... Resize correctly. Okay. Now I also want to add a couple buttons to my document. One to start capture. And then I want to have one that's going to stop capture.
Okay, so that's what my UI is going to look like. And... That's all right, this is on stage. What do you want? And I need to make some outlets and actions, so I'll do that like this.
[Transcript missing]
First one, of course, is start capturing. And I want another one. Guess what that's gonna be called? Okay.
And now I gotta make up my, hook up my connections. I will control drag from the there to there. And yes, that's good. That is the capture view. And then I will control drag from the button to here. Start capturing. Very good. And control drag from here to there. And that is indeed stop capturing.
So I think I've got that all correct. Now we need to write some code. Let's go into my document. Now remember I said we're going to have three main objects running around. We're going to have a capture session. We're going to have some inputs and some outputs. Oh, you can't see that, can you? So in my controllerDidLoadNib method, I'll add this code.
[Transcript missing]
I have the capture view, and then I have those three objects that I talked about, the device input, the movie output, and the capture session. And then I'll declare the two actions that I want to implement. Start capturing and stop capturing. Okay, now let's go back to my didload nib. You'll see that the first thing I do is create a capture session object, and I create a device input object, and I create a movie output object. I then add the input to the capture session. I add the output to the capture session.
Next thing I need to do is add a device to the device input. So what I have done here is call the input devices method on QTCapture device. That's a class method, and it's going to give me back an array of all the available devices that it can find.
And here I'm just going to take the first one in the list. I don't know what it is, but that's what I'm going to take as my capture device. And then finally, I need to set the capture session onto the capture view. So now I've created the session, I've created the input, I've created the output, and I've hooked everything up correctly.
Okay, and then I think all I need to do now is implement my start capturing and stop capturing method. There's Start Capturing, real simple. I just call Start Capturing on the capture session, because that's what's handling everything. Stop capturing. A little more complicated, but not much. What am I going to want to do when I stop capturing? I'm going to want to...
[Transcript missing]
I hate when that happens. Oh, I know what I did wrong. Silly me. Although we added, The framework to our project, we did not include its headers.
So we will include QTKit, qtkit.h. And let's see if that builds. Linking, that sounds good. Build succeeded. All right, let's run it. Here comes our application. If all goes well, we should get preview from whatever the first device on the list was, and it seems to be this HDV Handycam.
[Transcript missing]
In fact, it's so good, you can see that it's a really old can of Campbell's soup. The expiration date is 1998. What it was doing in my closet, I have no idea. You probably want to see some actual capturing done, don't you? I am so sick of this dock. Turn on hiding. Thank you. All right, let's go back to where I can actually see the buttons. Oh, no.
So let's start capturing. Maybe get a little motion in there. We'll stop capturing. And where did we write it? We wrote it into /temp/capturedmovie. Is there a captured movie there? By golly, there is. Let's open it, see what we got. Oh, that's big. Yeah. All right, so it's so big it won't even fit on this monitor. So we successfully captured, I don't know how many seconds, of HDV video using, what, 20 lines of code? Was it even that much? Oh, you want to play it? Oh. You don't trust me? There we go.
All right, how brave am I? One feature that I didn't mention is we can capture from multiple devices simultaneously. Well, we almost have the code we need for that. Let's just change things here a little bit. Hide that. Almost a compression. Sorry? Uh, David? What was the compression scheme of that file? Straight off the camera. All the bits you ever wanted.
Okay, so we want to capture from multiple devices. I'm going to come over here. So we're going to have more than one device. So let's throw in some nice static variables that keep track of how many devices we have and how many files we're writing into. We don't want to write them all into the same file, do we? So now we have a device index.
And in our Awake from Nib, of course, we don't want to always open device zero, do we? We want to open device at device index. And since we don't always want zero, we'll increment that. So the next time through, we'll get the next device in the list, right? And we need to make one other change. This is going to be incredibly simple. Uh, duh-duh-duh-duh-duh-duh-duh. Okay.
So, let's just throw all of this away and replace it by this. So, when I'm done, I'm going to detach the captured movie from the current thread. Because I don't want to do this all on the main thread, I want to do it on the background thread, right? Let's use some of that code we wrote earlier. And then once I do that, I want to detach new thread selector. I want to call write it on a background thread, passing in the movie that I just captured.
And write it should look completely familiar to you right now, because it's just doing the stuff to send the movie to the background thread, write to file to write the file out to a new, sorry, to write the movie out to a new file. And finally, you can see that I'm putting an integer into the name so that I don't write it all into the same file. If I'm lucky, that will build.
Let me do one other thing here. We're capturing a lot of data. So let's just have it beep when it's done exporting all that, or flattening all that data, so we know when we can actually look at the file. So let's build that. And let's run it. See what happens.
So there's our first device. And this is a document-based application, so I can come up here and do new. So there's our first device. And this is a document-based application, so I can come up here and do new. I will start capturing HDV. I'll stop that. I'll stop this. Let's listen for a couple of beeps. I don't know if they have the sound on.
There's two beeps. All right. Now the proof of the pudding is in the eating. Do we have two files there? Captured movie one, captured movie two. Does the first one open? All right. And now let's go back to terminal. Let's open the second one. Excellent. So we got multiple capture from two different devices. Okay. Can we go back to slides, please? All right, maybe you've got a Carbon application.
Can you use this stuff? Sure, a Carbon application can link against a Cocoa framework. Let me just quickly demo this. Can I go back to demo machine, please? I'm not going to show you the code for these. I'm just going to-- does that say Carbon capture? So here's a Carbon application that is putting up a QT capture view. Because of the way we wrote it, it's not actually previewing yet. But when I start capture, you will get the preview.
I'm not going to show you the code for these. I'm just going to-- does that say Carbon capture? And there it is up there, recorded movie. I won't bother to open it because by now I think you trust me that it actually did capture the video it was supposed to.
And one last thing I want to show you is... And there it is up there, recorded movie. I won't bother to open it because by now I think you trust me that it actually did capture the video it was supposed to. And one last thing I want to show you is... So, let's get started. So, let's get started.
So, we're going to start with a QTKit And then we can give that a mask, position it, crank up its visibility. And there we see, if I crank that down, there is Brad playing. And so now we have composited onto an OpenGL surface live video and a stored QuickTime movie. Thank you.
All right, back to slides, please. So that was that demo that I just showed, capturing individual context. Okay, so what are the limitations? These APIs are still in development. It's a whole lot of new APIs. As I said, it's 11 new classes. We really are looking forward to your feedback on this. This is why you're here, and this is why we give you this stuff early, so we can elicit feedback.
The audio preview output is not working in the seed that you have. On-the-fly compression is not working in the seed you have. Many of the device and channel attributes I haven't even mentioned, but are in the documentation, are not currently working. And the feature that I talked about of frame exact capture is, again, not in the seed that you have.
So, let's just sum up. QTKit is getting better and better. In Leopard, it takes on the burden for giving you 64-bit operation, and it is the only way to get this brand new pro-grade capture engine that's in QuickTime. We're giving you new APIs as we see that you need them, and we're trying to sort of expand the collective wisdom. And as I said, your feedback is crucial for us to make this even better than it is now.